diff --git a/src/app/(main)/projects/create/page.tsx b/src/app/(main)/projects/create/page.tsx
index c737430..fef65c1 100644
--- a/src/app/(main)/projects/create/page.tsx
+++ b/src/app/(main)/projects/create/page.tsx
@@ -1,3 +1,4 @@
+"use client";
import { fetchProjectCategories } from "@/app/api/projects";
import { preloadStaff } from "@/app/api/staff";
import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks";
diff --git a/src/app/(main)/staff/create/page.tsx b/src/app/(main)/staff/create/page.tsx
index b2d6bc6..08bb1eb 100644
--- a/src/app/(main)/staff/create/page.tsx
+++ b/src/app/(main)/staff/create/page.tsx
@@ -60,42 +60,117 @@ const CreateStaff: React.FC = async () => {
{
id: "name",
label: t("Staff Name"),
- type: "textfield",
+ type: "text",
value: "asdasd",
// required: "asdasd",
// option: "asdasd",
},
{
- id: "date",
- label: "date test",
- type: "multiDate",
- value: "asdasd",
+ id: "currentPosition",
+ label: t("Current Position"),
+ type: "combo-Obj",
+ // value: "asdasd",
// required: "asdasd",
- // option: "asdasd",
+ options: [{id: 1, key: 1, value: 1, label: "Potato"}, {id: 2, key: 2, value: 2, label: "Tomato"}],
},
{
- id: "date2",
- label: "combo test",
+ id: "joinPosition",
+ label: t("Join Position"),
type: "combo-Obj",
- value: "asdasd",
+ // value: "asdasd",
// required: "asdasd",
- options: [{id: 1, key: 1, value: 1, label: "first"}, {id: 2, key: 1, value: 2, label: "second"}],
+ options: [{id: 1, key: 1, value: 1, label: "Potato"}, {id: 2, key: 2, value: 2, label: "Tomato"}],
},
{
- id: "field1",
- label: "remarks test",
+ 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"}],
+ },
+ {
+ 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"}],
+ },
+ {
+ 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"}],
+ },
+ {
+ id: "salaryEffId",
+ label: t("Salary point ID with effective date"),
+ type: "combo-Obj",
+ options: [{id: 1, key: 1, value: 1, label: t("first")}, {id: 2, key: 2, value: 2, label: t("second")}],
+ },
+ {
+ id: "hourlyRate",
+ label: t("Hourly Rate"),
+ type: "numeric",
+ value: "",
+ },
+ {
+ id: "email",
+ label: t("Email"),
+ type: "text",
+ value: "",
+ },
+ {
+ id: "phone1",
+ label: t("Phone1"),
+ type: "numeric",
+ value: "",
+ },
+ {
+ id: "phone2",
+ label: t("Phone2"),
+ type: "numeric",
+ value: "",
+ },
+ {
+ id: "emergContactName",
+ label: t("Emergency Contact Name"),
+ type: "text",
+ value: "",
+ },
+ {
+ id: "emergContactPhone",
+ label: t("Emergency Contact Phone"),
+ type: "numeric",
+ value: "",
+ },
+ {
+ 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")}],
+ value: "",
+ },
+ {
+ id: "departDate",
+ label: t("Depart Date"),
+ type: "multiDate",
+ value: "",
+ },
+ {
+ id: "departReason",
+ label: t("Depart Reason"),
+ type: "text",
+ value: "",
+ },
+ {
+ id: "remark",
+ label: t("Remark"),
type: "remarks",
value: "",
- // required: "asdasd",
- // options: "asdasd",
},
],
];
- const handleSubmit = (data: any) => {
- console.log(data);
- };
-
return (
<>
{t("Create Staff")}
diff --git a/src/app/(main)/staff/page.tsx b/src/app/(main)/staff/page.tsx
new file mode 100644
index 0000000..13804ce
--- /dev/null
+++ b/src/app/(main)/staff/page.tsx
@@ -0,0 +1,48 @@
+// 'use client';
+import { preloadClaims } from "@/app/api/claims";
+import { preloadStaff, preloadTeamLeads } from "@/app/api/staff";
+import StaffSearch from "@/components/StaffSearch";
+import { getServerI18n } from "@/i18n";
+import Add from "@mui/icons-material/Add";
+import Button from "@mui/material/Button";
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import { Metadata } from "next";
+import Link from "next/link";
+import { Suspense } from "react";
+
+export const metadata: Metadata = {
+ title: "Staff",
+};
+
+const Staff: React.FC = async () => {
+ const { t } = await getServerI18n("projects");
+ preloadTeamLeads()
+ return (
+ <>
+
+
+ {t("Staff")}
+
+ }
+ LinkComponent={Link}
+ href="/staff/create"
+ >
+ {t("Create Staff")}
+
+
+ }>
+
+
+ >
+ );
+};
+
+export default Staff;
diff --git a/src/app/api/staff/actions.ts b/src/app/api/staff/actions.ts
new file mode 100644
index 0000000..0547d66
--- /dev/null
+++ b/src/app/api/staff/actions.ts
@@ -0,0 +1,39 @@
+"use server";
+import { serverFetchJson } from "@/app/utils/fetchUtil";
+import { BASE_API_URL } from "@/config/api";
+
+export interface CreateCustomInputs {
+ // Project details
+ projectCode: string;
+ projectName: string;
+
+ // Miscellaneous
+ expectedProjectFee: string;
+ }
+export interface CreateStaffInputs {
+ name: string;
+ currentPositionId: number;
+ joinPositionId: number;
+ companyId: number;
+ gradeId: number;
+ teamId: number;
+ salaryEffId: number;
+ email: string;
+ phone1: string;
+ phone2: string;
+ emergContactName: string;
+ emergContactPhone: string;
+ employType: string;
+ departDate: string;
+ departReason: string;
+ remark: string;
+ }
+
+
+export const saveStaff = async (data: CreateStaffInputs) => {
+ return serverFetchJson(`${BASE_API_URL}/staffs/new`, {
+ method: "POST",
+ body: JSON.stringify(data),
+ headers: { "Content-Type": "application/json" },
+ });
+ };
\ No newline at end of file
diff --git a/src/app/api/staff/index.ts b/src/app/api/staff/index.ts
index 617169b..7a1283c 100644
--- a/src/app/api/staff/index.ts
+++ b/src/app/api/staff/index.ts
@@ -13,8 +13,36 @@ export interface Staff {
};
}
-export const preloadStaff = () => {
+export interface resultObj {
+records: StaffResult[]
+}
+export interface StaffResult {
+ id: number;
+ created: string;
+ name: string;
+ cost: number;
+ staffId: string;
+ type: string;
+ currPos: string;
+ joinPos: string;
+ companyId: string;
+ skillSetId: number;
+ departmentId: number;
+ phone1: string;
+ phone2: string;
+ email: string;
+ emergContactName: string;
+ emergContactPhone: string;
+ employType: string;
+ departDate: string;
+ departReason: string;
+ remarks: string;
+}
+
+
+export const preloadTeamLeads = () => {
fetchTeamLeads();
+ fetchStaff();
};
export const fetchTeamLeads = cache(async () => {
@@ -22,3 +50,14 @@ export const fetchTeamLeads = cache(async () => {
next: { tags: ["teamLeads"] },
});
});
+
+export const preloadStaff = () => {
+ fetchStaff();
+};
+
+export const fetchStaff = cache(async () => {
+ return serverFetchJson(`${BASE_API_URL}/staffs/newlist`, {
+ next: { tags: ["teamLeads"] },
+ });
+});
+
diff --git a/src/components/CreateStaff/CreateStaffForm.tsx b/src/components/CreateStaff/CreateStaffForm.tsx
index e73cf1d..a44b5b2 100644
--- a/src/components/CreateStaff/CreateStaffForm.tsx
+++ b/src/components/CreateStaff/CreateStaffForm.tsx
@@ -1,6 +1,17 @@
-'use client'
-import { useState } from 'react';
-import CustomInputForm from '../CustomInputForm';
+"use client";
+import { useCallback, useState } from "react";
+import CustomInputForm from "../CustomInputForm";
+import { useRouter } from "next/navigation";
+import { useTranslation } from "react-i18next";
+import {
+ FieldErrors,
+ FormProvider,
+ SubmitErrorHandler,
+ SubmitHandler,
+ useForm,
+} from "react-hook-form";
+import { CreateStaffInputs, saveStaff } from "@/app/api/staff/actions";
+import { Typography } from "@mui/material";
interface Field {
// subtitle: string;
@@ -21,25 +32,48 @@ interface formProps {
fieldLists: Field[][];
}
-const CreateStaffForm: React.FC = ({
- fieldLists
-}) => {
+const CreateStaffForm: React.FC = ({ fieldLists }) => {
// const [formData, setFormData] = useState(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 handleSubmit = (data: any) => {
+ // console.log(data);
+ // // Handle the form submission logic here
+ // // setFormData(data);
+ // };
+ const handleCancel = () => {
+ router.back();
};
+ const onSubmit = useCallback>(
+ async (data) => {
+ try {
+ console.log(data);
+ setServerError("");
+ await saveStaff(data);
+ } catch (e) {
+ setServerError(t("An error has occurred. Please try again later."));
+ }
+ },
+ [router, t]
+ );
return (
-
+ <>
+ {serverError && (
+
+ {serverError}
+
+ )}
+
+ >
);
};
-export default CreateStaffForm;
\ No newline at end of file
+export default CreateStaffForm;
diff --git a/src/components/CustomInputForm/CustomInputForm.tsx b/src/components/CustomInputForm/CustomInputForm.tsx
index dc7f962..1e67f60 100644
--- a/src/components/CustomInputForm/CustomInputForm.tsx
+++ b/src/components/CustomInputForm/CustomInputForm.tsx
@@ -28,7 +28,7 @@ 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 { Check } from "@mui/icons-material";
+import { Check, Close } from "@mui/icons-material";
// interface Option {
// // Define properties of each option object
@@ -47,10 +47,13 @@ interface Field {
required?: boolean;
options?: any[];
readOnly?: boolean;
+ size?: number;
+ setValue?: any[];
}
interface CustomInputFormProps {
onSubmit: (data: any) => void;
+ onCancel: () => void;
// resetForm: () => void;
Title?: string[];
isActive: boolean;
@@ -62,6 +65,7 @@ const CustomInputForm: React.FC = ({
isActive,
fieldLists,
onSubmit,
+ onCancel,
// resetForm,
}) => {
const { t } = useTranslation();
@@ -130,6 +134,30 @@ const CustomInputForm: React.FC = ({
}));
};
+ const handleCancel = () => {
+ reset();
+ // resetForm();
+ // setFromDate(null);
+ setDateObj(null);
+ setValue({});
+ if (onCancel) {
+ onCancel();
+ }
+ // if fields include setValue func
+ // fieldLists.map((list) => {
+ // list.map((field) => {
+ // if (typeof field.setValue === 'function') {
+ // field.setValue(typeof field.value === 'boolean' ? false : null);
+ // } else if (typeof field.setValue === 'object') {
+ // field.setValue.map((setFunc: any) => {
+ // setFunc(null);
+ // });
+ // }
+ // })
+ // });
+ // setToDate(null);
+ };
+
fieldLists.forEach((list) => {
list.forEach((obj) => {
if (
@@ -168,9 +196,9 @@ const CustomInputForm: React.FC = ({
) : null}
{fieldList.map((field: Field) => {
- if (field.type === "textfield") {
+ if (field.type === "text") {
return (
-
+
= ({
);
} else if (field.type === "multiDate") {
return (
-
+
{
handleDateChange(field.id, newValue);
}}
@@ -202,7 +236,7 @@ const CustomInputForm: React.FC = ({
);
} else if (field.type === "combo-Obj") {
return (
-
+
{field.label}
@@ -259,7 +293,7 @@ const CustomInputForm: React.FC = ({
);
} else if (field.type === "numeric") {
return (
-
+
= ({
);
} else if (field.type === "numeric-positive") {
return (
-
+
= ({
);
} else if (field.type === "checkbox") {
return (
-
+
= ({
);
} else {
return (
-
+
= ({
))}
- {/* }
- onClick={handleCancel}
- >
- {t("Cancel")}
- */}
+ }
+ onClick={handleCancel}
+ >
+ {t("Cancel")}
+
} type="submit">
{t("Confirm")}
diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx
index 1c5b640..58c957d 100644
--- a/src/components/NavigationContent/NavigationContent.tsx
+++ b/src/components/NavigationContent/NavigationContent.tsx
@@ -17,6 +17,7 @@ import Assignment from "@mui/icons-material/Assignment";
import Settings from "@mui/icons-material/Settings";
import Analytics from "@mui/icons-material/Analytics";
import Payments from "@mui/icons-material/Payments";
+import Staff from "@mui/icons-material/PeopleAlt";
import { useTranslation } from "react-i18next";
import Typography from "@mui/material/Typography";
import { usePathname } from "next/navigation";
@@ -87,6 +88,7 @@ const navigationItems: NavigationItem[] = [
{ icon: , label: "Invoice", path: "/invoice" },
{ icon: , label: "Analysis Report", path: "/analytics" },
{ icon: , label: "Setting", path: "/settings" },
+ { icon: , label: "Staff", path: "/staff" },
];
const NavigationContent: React.FC = () => {
diff --git a/src/components/StaffSearch/StaffSearch.tsx b/src/components/StaffSearch/StaffSearch.tsx
new file mode 100644
index 0000000..ec55280
--- /dev/null
+++ b/src/components/StaffSearch/StaffSearch.tsx
@@ -0,0 +1,156 @@
+"use client";
+
+import { StaffResult } from "@/app/api/staff";
+import React, { useCallback, 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";
+
+interface Props {
+ staff: StaffResult[];
+}
+
+type SearchQuery = Partial>;
+type SearchParamNames = keyof SearchQuery;
+
+const StaffSearch: React.FC = ({ staff }) => {
+ const { t } = useTranslation("staff");
+
+ // If claim searching is done on the server-side, then no need for this.
+ const [filteredClaims, setFilteredClaims] = useState(staff);
+
+ const searchCriteria: Criterion[] = useMemo(
+ () => [
+ {
+ label: t("Staff Name"),
+ paramName: "name",
+ type: "text"
+ },
+ {
+ label: t("Cost (HKD)"),
+ paramName: "cost",
+ type: "text",
+ },
+ {
+ label: t("Expense Type"),
+ paramName: "type",
+ type: "select",
+ options: ["Expense", "Petty Cash"],
+ },
+ {
+ label: t("Current Position"),
+ paramName: "currPos",
+ type: "select",
+ options: ["Small Potato", "CEO"],
+ },
+ {
+ label: t("Join Position"),
+ paramName: "joinPos",
+ type: "select",
+ options: ["Small Potato", "CEO"],
+ },
+ {
+ label: t("Company"),
+ paramName: "companyId",
+ type: "select",
+ options: ["1", "2"],
+ },
+ // {
+ // label: t("Skillset"),
+ // paramName: "skillSetId",
+ // type: "select",
+ // options: ["Fly", "Boxing"],
+ // },
+ // {
+ // label: t("Department"),
+ // paramName: "departmentId",
+ // type: "select",
+ // options: ["Fly", "Boxing"],
+ // },
+ // {
+ // label: t("phone1"),
+ // paramName: "phone1",
+ // type: "text",
+ // },
+ // {
+ // label: t("phone2"),
+ // paramName: "phone2",
+ // type: "text",
+ // },
+ // {
+ // label: t("email"),
+ // paramName: "email",
+ // type: "text",
+ // },
+ // {
+ // label: t("Emergency Contact Name"),
+ // paramName: "emergContactName",
+ // type: "text",
+ // },
+ // {
+ // label: t("Emergency Contact Phone"),
+ // paramName: "emergContactPhone",
+ // type: "text",
+ // },
+ // {
+ // label: t("Employ Type"),
+ // paramName: "employType",
+ // type: "select",
+ // options: ["Full-time", "Part-time"],
+ // },
+ // {
+ // label: t("Depart Date"),
+ // paramName: "departDate",
+ // type: "date",
+ // },
+ // {
+ // label: t("Depart Reason"),
+ // paramName: "departReason",
+ // type: "text",
+ // },
+ // {
+ // label: t("Remarks"),
+ // paramName: "remarks",
+ // type: "text",
+ // },
+ ],
+ [t],
+ );
+
+ const onStaffClick = useCallback((staff: StaffResult) => {
+ console.log(staff);
+ }, []);
+
+ const columns = useMemo[]>(
+ () => [
+ // {
+ // name: "action",
+ // label: t("Actions"),
+ // onClick: onClaimClick,
+ // buttonIcon: ,
+ // },
+ { name: "created", label: t("Creation Date") },
+ { name: "name", label: t("Related Project Name") },
+ { name: "staffId", label: t("Staff Id") },
+ { name: "type", label: t("Expense Type") },
+ // { name: "status", label: t("Status") },
+ { name: "remarks", label: t("Remarks") },
+ ],
+ [t, onStaffClick],
+ );
+
+ return (
+ <>
+ {
+ console.log(query);
+ }}
+ />
+ items={filteredClaims} columns={columns} />
+ >
+ );
+};
+
+export default StaffSearch;
diff --git a/src/components/StaffSearch/StaffSearchLoading.tsx b/src/components/StaffSearch/StaffSearchLoading.tsx
new file mode 100644
index 0000000..45c5c6d
--- /dev/null
+++ b/src/components/StaffSearch/StaffSearchLoading.tsx
@@ -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 StaffSearchLoading: React.FC = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default StaffSearchLoading;
diff --git a/src/components/StaffSearch/StaffSearchWrapper.tsx b/src/components/StaffSearch/StaffSearchWrapper.tsx
new file mode 100644
index 0000000..5ffcd58
--- /dev/null
+++ b/src/components/StaffSearch/StaffSearchWrapper.tsx
@@ -0,0 +1,22 @@
+import { fetchStaff, fetchTeamLeads } from "@/app/api/staff";
+import React from "react";
+import StaffSearch from "./StaffSearch";
+import StaffSearchLoading from "./StaffSearchLoading";
+// import { preloadStaff } from "@/app/api/staff";
+
+interface SubComponents {
+ Loading: typeof StaffSearchLoading;
+}
+
+const StaffSearchWrapper: React.FC & SubComponents = async () => {
+ const staff = await fetchStaff();
+ // const try = ...staff
+ console.log(staff)
+ const records = staff.records;
+
+ return ;
+};
+
+StaffSearchWrapper.Loading = StaffSearchLoading;
+
+export default StaffSearchWrapper;
diff --git a/src/components/StaffSearch/index.ts b/src/components/StaffSearch/index.ts
new file mode 100644
index 0000000..ca9e06a
--- /dev/null
+++ b/src/components/StaffSearch/index.ts
@@ -0,0 +1 @@
+export { default } from "./StaffSearchWrapper";
diff --git a/src/i18n/zh/createStaff.json b/src/i18n/zh/createStaff.json
new file mode 100644
index 0000000..3dbc6a6
--- /dev/null
+++ b/src/i18n/zh/createStaff.json
@@ -0,0 +1,19 @@
+{
+ "Staff Name": "Staff Name",
+ "Current Position": "Current Position",
+ "Join Position": "Join Position",
+ "Company": "Company",
+ "Grade": "Grade",
+ "Team": "Team",
+ "Salary point ID with effective date": "Salary point ID with effective date",
+ "Hourly Rate": "Hourly Rate",
+ "Email": "Email",
+ "Phone1": "Phone1",
+ "Phone2": "Phone2",
+ "Emergency Contact Name": "Emergency Contact Name",
+ "Emergency Contact Phone": "Emergency Contact Phone",
+ "Employ Type": "Employ Type",
+ "Depart Date": "Depart Date",
+ "Depart Reason": "Depart Reason",
+ "Remark": "Remark"
+ }
\ No newline at end of file