Przeglądaj źródła

update staff edit

tags/Baseline_30082024_FRONTEND_UAT
MSI\derek 1 rok temu
rodzic
commit
8295c5cf35
21 zmienionych plików z 598 dodań i 57 usunięć
  1. +2
    -6
      src/app/(main)/settings/staff/create/page.tsx
  2. +29
    -8
      src/app/(main)/settings/staff/edit/page.tsx
  3. +0
    -1
      src/app/(main)/settings/staff/page.tsx
  4. +19
    -0
      src/app/api/companys/actions.ts
  5. +18
    -1
      src/app/api/departments/actions.ts
  6. +20
    -0
      src/app/api/grades/actions.ts
  7. +18
    -1
      src/app/api/positions/actions.ts
  8. +21
    -0
      src/app/api/skill/actions.ts
  9. +19
    -6
      src/app/api/staff/actions.ts
  10. +6
    -0
      src/app/api/staff/index.ts
  11. +19
    -0
      src/app/api/team/actions.ts
  12. +13
    -12
      src/components/CustomInputForm/CustomInputForm.tsx
  13. +244
    -0
      src/components/EditStaff/EditStaff.tsx
  14. +40
    -0
      src/components/EditStaff/EditStaffLoading.tsx
  15. +19
    -0
      src/components/EditStaff/EditStaffWrapper.tsx
  16. +1
    -0
      src/components/EditStaff/index.ts
  17. +83
    -0
      src/components/EditStaffForm/EditStaffForm.tsx
  18. +1
    -0
      src/components/EditStaffForm/index.ts
  19. +19
    -16
      src/components/StaffSearch/StaffSearch.tsx
  20. +2
    -3
      src/components/StaffSearch/StaffSearchWrapper.tsx
  21. +5
    -3
      src/i18n/zh/staff.json

+ 2
- 6
src/app/(main)/settings/staff/create/page.tsx Wyświetl plik

@@ -32,11 +32,7 @@ interface CreateCustomInputs {
projectCode: string;
projectName: string;
}
const createCustomInputs: CreateCustomInputs = {
// Project details
projectCode: "",
projectName: "",
}

// const Title = ["title1", "title2"];

const CreateStaff: React.FC = async () => {
@@ -58,7 +54,7 @@ const CreateStaff: React.FC = async () => {
id: "name",
label: t("Staff Name"),
type: "text",
value: "asdasd",
value: "",
required: true,
},
{


+ 29
- 8
src/app/(main)/settings/staff/edit/page.tsx Wyświetl plik

@@ -1,10 +1,31 @@
const EditStaff: React.FC = async () => {
// "use client";
import { Edit } from "@mui/icons-material";
import { useSearchParams } from "next/navigation";
import EditStaff from "@/components/EditStaff";
import { Suspense } from "react";
import { I18nProvider } from "@/i18n";
import EditStaffWrapper from "@/components/EditStaff/EditStaffWrapper";
import { Metadata } from "next";

return (
<>
sdsadasd
</>
)
}
// export const metadata: Metadata = {
// title: "staff-edit",
// };

export default EditStaff;
const EditStaffPage: React.FC = () => {
// const searchParams = useSearchParams();
// const userId = searchParams.get('param');
// console.log(userId); // Access the value of the "user_id" parameter

return (
<>
<I18nProvider namespaces={["staff", "common"]}>
<Suspense fallback={<EditStaff.Loading />}>
<EditStaff />
</Suspense>
</I18nProvider>
{/* <EditStaff /> */}
</>
);
};

export default EditStaffPage;

+ 0
- 1
src/app/(main)/settings/staff/page.tsx Wyświetl plik

@@ -1,4 +1,3 @@
// 'use client';
import { preloadClaims } from "@/app/api/claims";
import { preloadStaff, preloadTeamLeads } from "@/app/api/staff";
import StaffSearch from "@/components/StaffSearch";


+ 19
- 0
src/app/api/companys/actions.ts Wyświetl plik

@@ -0,0 +1,19 @@
"use server";
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";

export interface comboProp {
id: any;
label: string;
}

export interface combo {
records: comboProp;
}

export const fetchCompanyCombo = cache(async () => {
return serverFetchJson<combo>(`${BASE_API_URL}/companys/combo`, {
next: { tags: ["company"] },
});
});

+ 18
- 1
src/app/api/departments/actions.ts Wyświetl plik

@@ -2,7 +2,17 @@

import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";


export interface comboProp {
id: any;
label: string;
}

export interface combo {
records: comboProp;
}
export interface CreateDepartmentInputs {
departmentCode: string;
departmentName: string;
@@ -15,4 +25,11 @@ export const saveDepartment = async (data: CreateDepartmentInputs) => {
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
};
};


export const fetchDepartmentCombo = cache(async () => {
return serverFetchJson<combo>(`${BASE_API_URL}/departments/combo`, {
next: { tags: ["department"] },
});
});

+ 20
- 0
src/app/api/grades/actions.ts Wyświetl plik

@@ -0,0 +1,20 @@
"use server"

import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";

export interface comboProp {
id: any;
label: string;
}

export interface combo {
records: comboProp;
}
export const fetchGradeCombo = cache(async () => {
return serverFetchJson<combo>(`${BASE_API_URL}/grades/combo`, {
next: { tags: ["grades"] },
});
});

+ 18
- 1
src/app/api/positions/actions.ts Wyświetl plik

@@ -2,6 +2,16 @@

import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";

export interface comboProp {
id: any;
label: string;
}

export interface combo {
records: comboProp;
}

export interface CreatePositionInputs {
positionCode: string;
@@ -15,4 +25,11 @@ export const savePosition = async (data: CreatePositionInputs) => {
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
};
};

export const fetchPositionCombo = cache(async () => {
return serverFetchJson<combo>(`${BASE_API_URL}/positions/combo`, {
next: { tags: ["positions"] },
});
});

+ 21
- 0
src/app/api/skill/actions.ts Wyświetl plik

@@ -0,0 +1,21 @@

"use server"

import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";

export interface comboProp {
id: any;
label: string;
}

export interface combo {
records: comboProp;
}
export const fetchSkillCombo = cache(async () => {
return serverFetchJson<combo>(`${BASE_API_URL}/skill/combo`, {
next: { tags: ["skill"] },
});
});

+ 19
- 6
src/app/api/staff/actions.ts Wyświetl plik

@@ -1,7 +1,8 @@
"use server";
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { StaffResult } from ".";
import { StaffResult, data } from ".";
import { cache } from "react";
export interface CreateCustomInputs {
// Project details
projectCode: string;
@@ -33,17 +34,29 @@ export interface CreateStaffInputs {
}
export const saveStaff = async (data: CreateStaffInputs) => {
return serverFetchJson(`${BASE_API_URL}/staffs/new`, {
return serverFetchJson(`${BASE_API_URL}/staffs/save`, {
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}`, {
export const deleteStaff = async (data: StaffResult) => {
return serverFetchJson(`${BASE_API_URL}/staffs/delete/${data.id}`, {
method: "DELETE",
// body: JSON.stringify(id),
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
};
};

export const fetchStaffEdit = cache(async (id: number) => {
return serverFetchJson<data>(`${BASE_API_URL}/staffs/${id}`, {
next: { tags: ["staffs"] },
});
});

// export const preloadStaffEdit = (id: number) => {
// fetchStaffEdit(id);
// };


+ 6
- 0
src/app/api/staff/index.ts Wyświetl plik

@@ -3,6 +3,9 @@ import { BASE_API_URL } from "@/config/api";
import { cache } from "react";
import "server-only";

export interface data {
[key: string]: any;
}
export interface StaffResult {
action: any;
id: number;
@@ -12,6 +15,7 @@ export interface StaffResult {
grade: string;
joinPosition: string;
currentPosition: string;
data: data
}
export interface searchInput {
staffId: string;
@@ -41,3 +45,5 @@ export const fetchStaff = cache(async () => {
});
});




+ 19
- 0
src/app/api/team/actions.ts Wyświetl plik

@@ -0,0 +1,19 @@
"use server";
import { serverFetchJson } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";

export interface comboProp {
id: any;
label: string;
}

export interface combo {
records: comboProp;
}

export const fetchTeamCombo = cache(async () => {
return serverFetchJson<combo>(`${BASE_API_URL}/team/combo`, {
next: { tags: ["team"] },
});
});

+ 13
- 12
src/components/CustomInputForm/CustomInputForm.tsx Wyświetl plik

@@ -32,15 +32,13 @@ 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
// // based on your specific requirements
// id: any;
// value: any;
// label: string;
// }
interface Options {
id: any;
label: string;
[key: string]: any;
}

interface Field {
export interface Field {
// subtitle: string;
id: string;
label: string;
@@ -49,7 +47,7 @@ interface Field {
required?: boolean;
pattern?: string;
message?: string;
options?: any[];
options?: Options[] | null;
readOnly?: boolean;
size?: number;
setValue?: any[];
@@ -249,7 +247,10 @@ const CustomInputForm: React.FC<CustomInputFormProps> = ({
? new RegExp(field.pattern)
: /.*/,
})}
defaultValue={!field.value ? `${field.value}` : ""}
defaultValue={field.value ? `${field.value}` : ""}
inputProps={{
readOnly: field.readOnly,
}}
required={field.required ?? false}
error={Boolean(errors[field.id])}
helperText={
@@ -347,9 +348,9 @@ const CustomInputForm: React.FC<CustomInputFormProps> = ({
: option
}
>
{option.id !== undefined
{option.id
? option.label
: option}
: ""}
</MenuItem>
))}
</Select>


+ 244
- 0
src/components/EditStaff/EditStaff.tsx Wyświetl plik

@@ -0,0 +1,244 @@
"use client";
import EditStaffForm from "../EditStaffForm";
import { useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import { BASE_API_URL } from "@/config/api";
import { fetchStaffEdit } from "@/app/api/staff/actions";
import { getServerI18n } from "@/i18n";
import { useTranslation } from "react-i18next";
import { comboProp, fetchCompanyCombo } from "@/app/api/companys/actions";
import { fetchTeamCombo } from "@/app/api/team/actions";
import { fetchDepartmentCombo } from "@/app/api/departments/actions";
import { fetchPositionCombo } from "@/app/api/positions/actions";
import { fetchGradeCombo } from "@/app/api/grades/actions";
import { fetchSkillCombo } from "@/app/api/skill/actions";
// import { Field } from "react-hook-form";

interface dataType {
[key: string]: any;
}


interface Options {
id: any;
label: string;
[key: string]: any;
}
// interface Field {
// id: string;
// label: string;
// type: string;
// value: any;
// required?: boolean;
// options?: comboProp[] | undefined | null;
// readOnly?: boolean;
// }

export interface Field {
// subtitle: string;
id: string;
label: string;
type: string;
value?: any;
required?: boolean;
pattern?: string;
message?: string;
options?: Options[]| null;
readOnly?: boolean;
size?: number;
setValue?: any[];
}

const EditStaff: React.FC = async () => {
const searchParams = useSearchParams();
const { t } = useTranslation();
const idString = searchParams.get("id");
const [id, setId] = useState(0);
const [fieldLists, setFieldLists] = useState<Field[][]>();
const [companyCombo, setCompanyCombo] = useState<comboProp>();
const [teamCombo, setTeamCombo] = useState<comboProp>();
const [departmentCombo, setDepartmentCombo] = useState<comboProp>();
const [positionCombo, setPositionCombo] = useState<comboProp>();
const [gradeCombo, setGradeCombo] = useState<comboProp>();
const [skillCombo, setSkillCombo] = useState<comboProp>();
// const employTypeCombo = [{id: "FT", label: t("FT")}, {id: "PT", label: t("PT")}];
const employTypeCombo = [{id: "FT", label: t("FT")}, {id: "PT", label: t("PT")}];

//fetch all combo
useEffect(() => {
fetchCompanyCombo().then((data) => {
console.log(data.records);
setCompanyCombo(data.records);
});
fetchTeamCombo().then((data) => {
setTeamCombo(data.records);
})
fetchDepartmentCombo().then((data) => {
setDepartmentCombo(data.records);
})
fetchPositionCombo().then((data) => {
setPositionCombo(data.records);
})
fetchGradeCombo().then((data) => {
setGradeCombo(data.records);
})
fetchSkillCombo().then((data) => {
setSkillCombo(data.records);
})
}, [searchParams]);

useEffect(() => {
let id = 0;
if (idString) {
id = parseInt(idString);
setId(id)
}
fetchStaffEdit(id).then((staff) => {
console.log(staff.data);
const data = staff.data;
const keyOrder = [
"staffId",
"name",
"company",
"team",
"department",
"grade",
"skill",
"currentPosition",
"salaryEffective",
"hourlySalary",
"employType",
"email",
"phone1",
"phone2",
];
const list1 = keyOrder
.map((key) => {
switch (key) {
case "staffId":
return {
id: `${key}`,
label: t(`${key}`),
type: "text",
value: data[key],
};
case "name":
return {
id: `${key}`,
label: t(`${key}`),
type: "text",
value: data[key],
};
case "company":
return {
id: `${key}`,
label: t(`${key}`),
type: "combo-Obj",
options: companyCombo,
value: data[key].id,
};
case "team":
return {
id: `${key}`,
label: t(`${key}`),
type: "combo-Obj",
options: teamCombo,
value: data[key].id,
};
case "department":
// console.log(data[key])
return {
id: `${key}`,
label: t(`${key}`),
type: "combo-Obj",
options: departmentCombo,
value: data[key]?.id ?? "",
// later check
};
case "grade":
return {
id: `${key}`,
label: t(`${key}`),
type: "combo-Obj",
options: gradeCombo,
value: data[key].id,
};
case "skill":
return {
id: `${key}`,
label: t(`${key}`),
type: "combo-Obj",
options: skillCombo,
value: data[key].id,
};
case "currentPosition":
return {
id: `${key}`,
label: t(`${key}`),
type: "combo-Obj",
options: positionCombo,
value: data[key].id,
};
case "salaryEffective":
return {
id: `${key}`,
label: t(`${key}`),
type: "text",
value: data[key],
};
case "hourlySalary":
return {
id: `${key}`,
label: t(`${key}`),
type: "text",
// value: data[key],
readOnly: true
};
case "employType":
return {
id: `${key}`,
label: t(`${key}`),
type: "combo-Obj",
options: employTypeCombo,
value: data[key],
};
case "email":
return {
id: `${key}`,
label: t(`${key}`),
type: "text",
value: data[key],
};
case "phone1":
return {
id: `${key}`,
label: t(`${key}`),
type: "text",
value: data[key],
};
case "phone2":
return {
id: `${key}`,
label: t(`${key}`),
type: "text",
value: data[key],
} as Field;
// Add more cases for each property
default:
return null;
}
})
.filter((item): item is Field => item !== null);
console.log([list1]);
setFieldLists([list1]);
});
}, [companyCombo]);

return (
<>
<EditStaffForm id={id} fieldLists={fieldLists || [[]]} />
</>
);
};

export default EditStaff;

+ 40
- 0
src/components/EditStaff/EditStaffLoading.tsx Wyświetl plik

@@ -0,0 +1,40 @@
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Skeleton from "@mui/material/Skeleton";
import Stack from "@mui/material/Stack";
import React from "react";

// Can make this nicer
export const EditStaffLoading: React.FC = () => {
return (
<>
<Card>
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton variant="rounded" height={60} />
<Skeleton
variant="rounded"
height={50}
width={100}
sx={{ alignSelf: "flex-end" }}
/>
</Stack>
</CardContent>
</Card>
<Card>EditStaff
<CardContent>
<Stack spacing={2}>
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
<Skeleton variant="rounded" height={40} />
</Stack>
</CardContent>
</Card>
</>
);
};

export default EditStaffLoading;

+ 19
- 0
src/components/EditStaff/EditStaffWrapper.tsx Wyświetl plik

@@ -0,0 +1,19 @@
import React from "react";
import EditStaff from "./EditStaff";
import EditStaffLoading from "./EditStaffLoading";
import { fetchStaff, fetchTeamLeads } from "@/app/api/staff";
import { useSearchParams } from "next/navigation";

interface SubComponents {
Loading: typeof EditStaffLoading;
}

const EditStaffWrapper: React.FC & SubComponents = async () => {


return <EditStaff/>;
};

EditStaffWrapper.Loading = EditStaffLoading;

export default EditStaffWrapper;

+ 1
- 0
src/components/EditStaff/index.ts Wyświetl plik

@@ -0,0 +1 @@
export { default } from "./EditStaffWrapper";

+ 83
- 0
src/components/EditStaffForm/EditStaffForm.tsx Wyświetl plik

@@ -0,0 +1,83 @@
// import { fetchStaffEdit } from "@/app/api/staff";
import { useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import CustomInputForm from "../CustomInputForm";
import { SubmitErrorHandler, SubmitHandler } from "react-hook-form";
import { CreateStaffInputs, saveStaff } from "@/app/api/staff/actions";
import { Typography } from "@mui/material";
import { useTranslation } from "react-i18next";

interface Options {
id: any;
label: string;
[key: string]: any;
}

interface Field {
// subtitle: string;
id: string;
label: string;
type: string;
value?: any;
required?: boolean;
options?: any[] | null;
readOnly?: boolean;
}

interface formProps {
id: number;
Title?: string[];
fieldLists: Field[][];
}

const EditStaffForm: React.FC<formProps> = ({ id, Title, fieldLists }) => {
const router = useRouter();
const { t } = useTranslation();
const [serverError, setServerError] = useState("");
// make new inputs
const onSubmit = useCallback<SubmitHandler<CreateStaffInputs>>(
async (data) => {
try {
console.log(data);
const temp = {
id: id,
...data
}
console.log(temp)
setServerError("");
await saveStaff(temp);
} catch (e) {
setServerError(t("An error has occurred. Please try again later."));
}
},
[router, t]
);
const onSubmitError = useCallback<SubmitErrorHandler<CreateStaffInputs>>(
(errors) => {
console.log(errors);
},
[]
);

const handleCancel = () => {
router.back();
};
return (
<>
{serverError && (
<Typography variant="body2" color="error" alignSelf="flex-end">
{serverError}
</Typography>
)}
<CustomInputForm
Title={Title}
fieldLists={fieldLists}
isActive={true}
onSubmit={onSubmit}
onSubmitError={onSubmitError}
onCancel={handleCancel}
/>
</>
);
};
export default EditStaffForm;

+ 1
- 0
src/components/EditStaffForm/index.ts Wyświetl plik

@@ -0,0 +1 @@
export { default } from "./EditStaffForm";

+ 19
- 16
src/components/StaffSearch/StaffSearch.tsx Wyświetl plik

@@ -9,6 +9,7 @@ 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";
import { useRouter } from "next/navigation";

interface Props {
staff: StaffResult[];
@@ -20,8 +21,9 @@ type SearchParamNames = keyof SearchQuery;
const StaffSearch: React.FC<Props> = ({ staff }) => {
const { t } = useTranslation();
const [filteredStaff, setFilteredStaff] = useState(staff);
const [id, setId] = useState(0);
const [data, setData] = useState<StaffResult>();
const [isOpen, setIsOpen] = useState(false);
const router = useRouter();

const searchCriteria: Criterion<SearchParamNames>[] = useMemo(
() => [
@@ -59,31 +61,32 @@ const StaffSearch: React.FC<Props> = ({ staff }) => {

const onStaffClick = useCallback((staff: StaffResult) => {
console.log(staff);
}, []);
const id = staff.id
router.push(`/settings/staff/edit?id=${id}`);
}, [router, t]);

const deleteClick = (staff: StaffResult) => {
console.log(staff.id);
const temp = staff.id
console.log(temp)
setId(temp)
console.log(staff);
setData(staff)
setIsOpen(!isOpen)
};
const onConfirm = (staff: StaffResult) => {

const onConfirm = useCallback(async (staff: StaffResult) => {
console.log(staff);
console.log(id);
deleteStaff(id)
// setIsOpen(!isOpen)
}
// if (data)
// await deleteStaff(data)
window.location.reload;
}, [deleteStaff, data]);

const onCancel = useCallback((staff: StaffResult) => {
console.log(staff);
// setId(0)
setIsOpen(false)
}, []);

useEffect(() => {
console.log("id");
console.log(id);
}, [id]);
// useEffect(() => {
// console.log("id");
// console.log(id);
// }, [id]);

const columns = useMemo<Column<StaffResult>[]>(
() => [


+ 2
- 3
src/components/StaffSearch/StaffSearchWrapper.tsx Wyświetl plik

@@ -1,3 +1,4 @@

import { fetchStaff, fetchTeamLeads } from "@/app/api/staff";
import React from "react";
import StaffSearch from "./StaffSearch";
@@ -10,9 +11,7 @@ interface SubComponents {

const StaffSearchWrapper: React.FC & SubComponents = async () => {
const staff = await fetchStaff();
// const try = ...staff
console.log(staff)
// const records = staff.records;
// console.log(staff)
return <StaffSearch staff={staff} />;
};


+ 5
- 3
src/i18n/zh/staff.json Wyświetl plik

@@ -12,8 +12,8 @@
"Skillset": "技能",
"Salary Point": "薪金點",
"Employ Type": "職位類別",
"Hourly Rate": "時薪",
"Email": "時薪",
"hourlyRate": "時薪",
"Email": "電郵地址",
"Phone1": "聯絡電話",
"Phone2": "次要聯絡電話",
"Additional Info": "更多資料",
@@ -23,5 +23,7 @@
"Join Position": "入職職位",
"Depart Date": "離職日期",
"Depart Reason": "離職原因",
"Remark": "備註"
"Remark": "備註",
"Confirm": "確認",
"Cancel": "取消"
}

Ładowanie…
Anuluj
Zapisz