diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx
index b93ed10..98a0800 100644
--- a/src/app/(main)/layout.tsx
+++ b/src/app/(main)/layout.tsx
@@ -6,6 +6,7 @@ import Box from "@mui/material/Box";
import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig";
import Stack from "@mui/material/Stack";
import Breadcrumb from "@/components/Breadcrumb";
+import { I18nProvider } from "@/i18n";
export default async function MainLayout({
children,
@@ -31,10 +32,12 @@ export default async function MainLayout({
padding: { xs: "1rem", sm: "1.5rem", lg: "3rem" },
}}
>
-
-
- {children}
-
+
+
+
+ {children}
+
+
>
);
diff --git a/src/app/(main)/settings/skill/create/page.tsx b/src/app/(main)/settings/skill/create/page.tsx
index c98f993..c912af3 100644
--- a/src/app/(main)/settings/skill/create/page.tsx
+++ b/src/app/(main)/settings/skill/create/page.tsx
@@ -28,11 +28,7 @@ import CreateSkill from "@/components/CreateSkill";
// const Title = ["title1", "title2"];
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}$")
- // console.log(regex)
+ const { t } = await getServerI18n("skill");
return (
<>
diff --git a/src/app/(main)/settings/skill/edit/page.tsx b/src/app/(main)/settings/skill/edit/page.tsx
index 05ff61f..7834b4f 100644
--- a/src/app/(main)/settings/skill/edit/page.tsx
+++ b/src/app/(main)/settings/skill/edit/page.tsx
@@ -17,13 +17,13 @@ const EditSkillPage: React.FC = async ({
searchParams,
}) => {
console.log(searchParams.id)
- const { t } = await getServerI18n("staff");
+ const { t } = await getServerI18n("skill");
return (
<>
{t("Edit Skill")}
-
+
}>
diff --git a/src/app/(main)/settings/skill/page.tsx b/src/app/(main)/settings/skill/page.tsx
index f263c87..bcb14ab 100644
--- a/src/app/(main)/settings/skill/page.tsx
+++ b/src/app/(main)/settings/skill/page.tsx
@@ -38,7 +38,7 @@ const Skill: React.FC = async () => {
{t("Create Skill")}
-
+
}>
diff --git a/src/app/(main)/settings/team/create/page.tsx b/src/app/(main)/settings/team/create/page.tsx
index a47d81c..f748270 100644
--- a/src/app/(main)/settings/team/create/page.tsx
+++ b/src/app/(main)/settings/team/create/page.tsx
@@ -1,28 +1,6 @@
-// 'use client';
import { I18nProvider, getServerI18n } from "@/i18n";
-import CustomInputForm from "@/components/CustomInputForm";
-import Check from "@mui/icons-material/Check";
-import Close from "@mui/icons-material/Close";
-import Button from "@mui/material/Button";
-import Stack from "@mui/material/Stack";
-import Tab from "@mui/material/Tab";
-import Tabs, { TabsProps } from "@mui/material/Tabs";
-import { useRouter } from "next/navigation";
-import React, { useCallback, useState } from "react";
-import { useTranslation } from "react-i18next";
-import { Task, TaskTemplate } from "@/app/api/tasks";
-import {
- FieldErrors,
- FormProvider,
- SubmitErrorHandler,
- SubmitHandler,
- useForm,
-} from "react-hook-form";
-import { CreateProjectInputs, saveProject } from "@/app/api/projects/actions";
-import { Error } from "@mui/icons-material";
-import { ProjectCategory } from "@/app/api/projects";
-import { Grid, Typography } from "@mui/material";
-import CreateStaffForm from "@/components/CreateStaff/CreateStaff";
+import React from "react";
+import { Typography } from "@mui/material";
import CreateTeam from "@/components/CreateTeam";
const CreateTeamPage: React.FC = async () => {
@@ -31,7 +9,7 @@ const CreateTeamPage: React.FC = async () => {
return (
<>
{t("Create Team")}
-
+
>
diff --git a/src/app/(main)/settings/team/page.tsx b/src/app/(main)/settings/team/page.tsx
index 5e78fb3..b2b67ab 100644
--- a/src/app/(main)/settings/team/page.tsx
+++ b/src/app/(main)/settings/team/page.tsx
@@ -18,7 +18,7 @@ export const metadata: Metadata = {
const Team: React.FC = async () => {
- const { t } = await getServerI18n("Team");
+ const { t } = await getServerI18n("team");
// preloadTeamLeads();
// preloadStaff();
return (
@@ -41,7 +41,7 @@ export const metadata: Metadata = {
{t("Create Team")}
-
+
}>
diff --git a/src/app/api/customer/index.ts b/src/app/api/customer/index.ts
index 7ed4359..ed4d947 100644
--- a/src/app/api/customer/index.ts
+++ b/src/app/api/customer/index.ts
@@ -11,6 +11,8 @@ export interface Customer {
address: string | null;
district: string | null;
customerType: CustomerType;
+
+ contacts: Contact[];
}
export interface SaveCustomerResponse {
@@ -40,6 +42,7 @@ export interface Subsidiary {
district: string | null;
email: string | null;
subsidiaryType: SubsidiaryType;
+ subsidiaryContacts: Contact[];
}
export interface SubsidiaryTable {
diff --git a/src/app/api/group/actions.ts b/src/app/api/group/actions.ts
index 1122c16..eadfe7d 100644
--- a/src/app/api/group/actions.ts
+++ b/src/app/api/group/actions.ts
@@ -36,16 +36,20 @@ export const fetchAuth = cache(async (target: string, id?: number ) => {
});
export const saveGroup = async (data: CreateGroupInputs) => {
- return serverFetchJson(`${BASE_API_URL}/group/save`, {
- method: "POST",
- body: JSON.stringify(data),
- headers: { "Content-Type": "application/json" },
- });
+ const newGroup = serverFetchJson(`${BASE_API_URL}/group/save`, {
+ method: "POST",
+ body: JSON.stringify(data),
+ headers: { "Content-Type": "application/json" },
+ });
+ revalidateTag("group")
+ return newGroup
};
export const deleteGroup = async (id: number) => {
- return serverFetchWithNoContent(`${BASE_API_URL}/group/${id}`, {
+ const newGroup = serverFetchWithNoContent(`${BASE_API_URL}/group/${id}`, {
method: "DELETE",
headers: { "Content-Type": "application/json" },
});
+ revalidateTag("group")
+ return newGroup
};
\ No newline at end of file
diff --git a/src/app/api/projects/actions.ts b/src/app/api/projects/actions.ts
index c80dff0..c1be476 100644
--- a/src/app/api/projects/actions.ts
+++ b/src/app/api/projects/actions.ts
@@ -20,6 +20,8 @@ export interface CreateProjectInputs {
projectLeadId: number;
projectActualStart: string;
projectActualEnd: string;
+ projectStatus: string;
+ isClpProject: boolean;
// Project info
serviceTypeId: number;
@@ -28,11 +30,14 @@ export interface CreateProjectInputs {
locationId: number;
buildingTypeIds: number[];
workNatureIds: number[];
+ taskTemplateId?: number | "All";
// Client details
clientId: Customer["id"];
- clientContactId: number;
+ clientContactId?: number;
clientSubsidiaryId?: number;
+ subsidiaryContactId: number;
+ isSubsidiaryContact?: boolean;
// Allocation
totalManhour: number;
diff --git a/src/app/api/projects/index.ts b/src/app/api/projects/index.ts
index 30bd385..90d1f4a 100644
--- a/src/app/api/projects/index.ts
+++ b/src/app/api/projects/index.ts
@@ -12,6 +12,7 @@ export interface ProjectResult {
category: string;
team: string;
client: string;
+ status: string;
}
export interface ProjectCategory {
@@ -67,7 +68,6 @@ export interface AssignedProject extends ProjectWithTasks {
hoursSpent: number;
hoursSpentOther: number;
hoursAllocated: number;
- hoursAllocatedOther: number;
}
export const preloadProjects = () => {
diff --git a/src/app/api/reports/actions.ts b/src/app/api/reports/actions.ts
index 1c4a175..b06b66b 100644
--- a/src/app/api/reports/actions.ts
+++ b/src/app/api/reports/actions.ts
@@ -1,7 +1,7 @@
"use server";
import { serverFetchBlob, serverFetchJson } from "@/app/utils/fetchUtil";
-import { MonthlyWorkHoursReportRequest, ProjectCashFlowReportRequest } from ".";
+import { MonthlyWorkHoursReportRequest, ProjectCashFlowReportRequest,LateStartReportRequest } from ".";
import { BASE_API_URL } from "@/config/api";
export interface FileResponse {
@@ -32,5 +32,18 @@ export const fetchMonthlyWorkHoursReport = async (data: MonthlyWorkHoursReportRe
},
);
+ return reportBlob
+};
+
+export const fetchLateStartReport = async (data: LateStartReportRequest) => {
+ const reportBlob = await serverFetchBlob(
+ `${BASE_API_URL}/reports/downloadLateStartReport`,
+ {
+ method: "POST",
+ body: JSON.stringify(data),
+ headers: { "Content-Type": "application/json" },
+ },
+ );
+
return reportBlob
};
\ No newline at end of file
diff --git a/src/app/api/reports/index.ts b/src/app/api/reports/index.ts
index 8923829..1cea414 100644
--- a/src/app/api/reports/index.ts
+++ b/src/app/api/reports/index.ts
@@ -24,3 +24,14 @@ export interface MonthlyWorkHoursReportRequest {
yearMonth: string;
}
+export interface LateStartReportFilter {
+ remainedDays: number;
+ overdueDays: number;
+ team: string[];
+}
+
+export interface LateStartReportRequest {
+ team: string[];
+ client: string[];
+ date: any;
+}
diff --git a/src/app/api/skill/actions.ts b/src/app/api/skill/actions.ts
index 6a0deca..15a27a7 100644
--- a/src/app/api/skill/actions.ts
+++ b/src/app/api/skill/actions.ts
@@ -1,8 +1,9 @@
"use server"
-import { serverFetchJson } from "@/app/utils/fetchUtil";
+import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
+import { revalidateTag } from "next/cache";
import { cache } from "react";
export interface CreateSkillInputs {
@@ -29,9 +30,21 @@ export const fetchSkillCombo = cache(async () => {
export const saveSkill = async (data: CreateSkillInputs) => {
- return serverFetchJson(`${BASE_API_URL}/skill/save`, {
- method: "POST",
- body: JSON.stringify(data),
- headers: { "Content-Type": "application/json" },
- });
+ const newSkill = serverFetchJson(`${BASE_API_URL}/skill/save`, {
+ method: "POST",
+ body: JSON.stringify(data),
+ headers: { "Content-Type": "application/json" },
+ });
+ revalidateTag("skill")
+ return newSkill
+ };
+
+
+export const deleteSkill = async (id: number) => {
+ const newSkill = await serverFetchWithNoContent(`${BASE_API_URL}/skill/delete/${id}`, {
+ method: "DELETE",
+ headers: { "Content-Type": "application/json" },
+ });
+ revalidateTag("skill");
+ return newSkill
};
\ No newline at end of file
diff --git a/src/app/api/skill/index.ts b/src/app/api/skill/index.ts
index a235426..0ff8d1c 100644
--- a/src/app/api/skill/index.ts
+++ b/src/app/api/skill/index.ts
@@ -4,11 +4,12 @@ import { cache } from "react";
import "server-only";
export interface SkillResult {
- action: any;
+ action: unknown;
id: number;
name: string;
description: string;
code: string;
+ delete: unknown
}
export const preloadSkill = () => {
@@ -17,12 +18,12 @@ export interface SkillResult {
export const fetchSkill = cache(async () => {
return serverFetchJson(`${BASE_API_URL}/skill`, {
- next: { tags: ["sill"] },
+ next: { tags: ["skill"] },
});
});
export const fetchSkillDetail = cache(async (id: number) => {
return serverFetchJson(`${BASE_API_URL}/skill/${id}`, {
- next: { tags: ["sill"] },
+ next: { tags: ["skill"] },
});
});
\ No newline at end of file
diff --git a/src/app/api/staff/actions.ts b/src/app/api/staff/actions.ts
index 88375d0..fca9727 100644
--- a/src/app/api/staff/actions.ts
+++ b/src/app/api/staff/actions.ts
@@ -1,9 +1,10 @@
"use server";
-import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
+import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil";
import { BASE_API_URL } from "@/config/api";
import { StaffResult, data } from ".";
import { cache } from "react";
import { Team, staff } from "../team/actions";
+import { revalidateTag } from "next/cache";
export interface CreateCustomInputs {
// Project details
projectCode: string;
@@ -41,17 +42,27 @@ export interface CreateStaffInputs {
name: string;
// team: Team[];
}
- // export interface Staff4TransferList {
- // records: records[];
- // }
export const saveStaff = async (data: CreateStaffInputs) => {
- return serverFetchJson(`${BASE_API_URL}/staffs/save`, {
+ // try {
+ const newStaffList = await serverFetchJson(`${BASE_API_URL}/staffs/save`, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
});
+ console.log(newStaffList)
+ revalidateTag("staffs");
+ return newStaffList
+
+ // } catch (e: any) {
+ // console.log(e.response)
+ // throw new ServerFetchError(
+ // "Something went wrong fetching data in serverssssss.",
+ // e.response,
+ // );
+ // }
};
+
export const testing = async (data: CreateStaffInputs) => {
return serverFetchJson(`${BASE_API_URL}/staffs/testing`, {
@@ -62,11 +73,13 @@ export const testing = async (data: CreateStaffInputs) => {
};
export const deleteStaff = async (id: number) => {
- return serverFetchWithNoContent(`${BASE_API_URL}/staffs/delete/${id}`, {
- method: "DELETE",
- // body: JSON.stringify(data),
- headers: { "Content-Type": "application/json" },
- });
+ const newStaffList = await serverFetchWithNoContent(`${BASE_API_URL}/staffs/delete/${id}`, {
+ method: "DELETE",
+ // body: JSON.stringify(data),
+ headers: { "Content-Type": "application/json" },
+ });
+ revalidateTag("staffs");
+ return newStaffList
};
diff --git a/src/app/api/staff/index.ts b/src/app/api/staff/index.ts
index f24f187..6b15f42 100644
--- a/src/app/api/staff/index.ts
+++ b/src/app/api/staff/index.ts
@@ -38,6 +38,7 @@ export interface StaffResult {
data: data;
teamId: number;
staffName: string;
+ userId: number;
}
export interface searchInput {
staffId: string;
diff --git a/src/app/api/subsidiary/index.ts b/src/app/api/subsidiary/index.ts
index c83f4db..818bebf 100644
--- a/src/app/api/subsidiary/index.ts
+++ b/src/app/api/subsidiary/index.ts
@@ -10,7 +10,9 @@ export interface Customer {
brNo: string | null;
address: string | null;
district: string | null;
- customerType: CustomerType
+ customerType: CustomerType;
+
+ contacts: Contact[];
}
export interface CustomerTable {
@@ -40,7 +42,9 @@ export interface Subsidiary {
brNo: string | null;
address: string | null;
district: string | null;
- subsidiaryType: SubsidiaryType
+ subsidiaryType: SubsidiaryType;
+
+ contacts: Contact[];
}
export interface SaveSubsidiaryResponse {
diff --git a/src/app/api/team/actions.ts b/src/app/api/team/actions.ts
index 47e1a82..f57a17a 100644
--- a/src/app/api/team/actions.ts
+++ b/src/app/api/team/actions.ts
@@ -3,6 +3,7 @@ import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil
import { BASE_API_URL } from "@/config/api";
import { cache } from "react";
import { TeamResult } from ".";
+import { revalidateTag } from "next/cache";
export interface CreateTeamInputs {
@@ -45,17 +46,21 @@ export const fetchTeamCombo = cache(async () => {
});
export const saveTeam = async (data: CreateTeamInputs) => {
- return serverFetchJson(`${BASE_API_URL}/team/save`, {
- method: "POST",
- body: JSON.stringify(data),
- headers: { "Content-Type": "application/json" },
- });
+ const newTeam = serverFetchJson(`${BASE_API_URL}/team/save`, {
+ method: "POST",
+ body: JSON.stringify(data),
+ headers: { "Content-Type": "application/json" },
+ });
+ revalidateTag("team")
+ return newTeam
};
export const deleteTeam = async (id: number) => {
- return serverFetchWithNoContent(`${BASE_API_URL}/team/delete/${id}`, {
+ const newTeam = serverFetchWithNoContent(`${BASE_API_URL}/team/delete/${id}`, {
method: "DELETE",
headers: { "Content-Type": "application/json" },
});
+ revalidateTag("team")
+ return newTeam
};
diff --git a/src/app/api/user/actions.ts b/src/app/api/user/actions.ts
index 919634c..b6329a8 100644
--- a/src/app/api/user/actions.ts
+++ b/src/app/api/user/actions.ts
@@ -26,18 +26,22 @@ export const fetchUserDetails = cache(async (id: number) => {
});
export const editUser = async (id: number, data: UserInputs) => {
- return serverFetchWithNoContent(`${BASE_API_URL}/user/${id}`, {
- method: "PUT",
- body: JSON.stringify(data),
- headers: { "Content-Type": "application/json" },
- });
+ const newUser = serverFetchWithNoContent(`${BASE_API_URL}/user/${id}`, {
+ method: "PUT",
+ body: JSON.stringify(data),
+ headers: { "Content-Type": "application/json" },
+ });
+ revalidateTag("user")
+ return newUser
};
export const deleteUser = async (id: number) => {
- return serverFetchWithNoContent(`${BASE_API_URL}/user/${id}`, {
- method: "DELETE",
- headers: { "Content-Type": "application/json" },
- });
+ const newUser = serverFetchWithNoContent(`${BASE_API_URL}/user/${id}`, {
+ method: "DELETE",
+ headers: { "Content-Type": "application/json" },
+ });
+ revalidateTag("user")
+ return newUser
};
export const changePassword = async (data: any) => {
diff --git a/src/app/utils/fetchUtil.ts b/src/app/utils/fetchUtil.ts
index c1f310b..5e4533e 100644
--- a/src/app/utils/fetchUtil.ts
+++ b/src/app/utils/fetchUtil.ts
@@ -38,18 +38,23 @@ type FetchParams = Parameters;
export async function serverFetchJson(...args: FetchParams) {
const response = await serverFetch(...args);
-
if (response.ok) {
return response.json() as T;
} else {
+ const errorText = await response.text()
switch (response.status) {
case 401:
signOutUser();
+ case 422:
+ throw new ServerFetchError(
+ JSON.parse(errorText).error,
+ response
+ );
default:
- console.error(await response.text());
+ console.error(errorText);
throw new ServerFetchError(
"Something went wrong fetching data in server.",
- response,
+ response
);
}
}
diff --git a/src/app/utils/formatUtil.ts b/src/app/utils/formatUtil.ts
index 4d773e9..150b350 100644
--- a/src/app/utils/formatUtil.ts
+++ b/src/app/utils/formatUtil.ts
@@ -21,41 +21,52 @@ export const OUTPUT_DATE_FORMAT = "YYYY/MM/DD";
export const OUTPUT_TIME_FORMAT = "HH:mm:ss";
-export const convertDateToString = (date: Date, format: string = OUTPUT_DATE_FORMAT) => {
- return dayjs(date).format(format)
-}
+export const convertDateToString = (
+ date: Date,
+ format: string = OUTPUT_DATE_FORMAT,
+) => {
+ return dayjs(date).format(format);
+};
-export const convertDateArrayToString = (dateArray: number[], format: string = OUTPUT_DATE_FORMAT, needTime: boolean = false) => {
+export const convertDateArrayToString = (
+ dateArray: number[],
+ format: string = OUTPUT_DATE_FORMAT,
+ needTime: boolean = false,
+) => {
if (dateArray.length === 6) {
if (!needTime) {
- const dateString = `${dateArray[0]}-${dateArray[1]}-${dateArray[2]}`
- return dayjs(dateString).format(format)
+ const dateString = `${dateArray[0]}-${dateArray[1]}-${dateArray[2]}`;
+ return dayjs(dateString).format(format);
}
}
if (dateArray.length === 3) {
if (!needTime) {
- const dateString = `${dateArray[0]}-${dateArray[1]}-${dateArray[2]}`
- return dayjs(dateString).format(format)
+ const dateString = `${dateArray[0]}-${dateArray[1]}-${dateArray[2]}`;
+ return dayjs(dateString).format(format);
}
}
-}
+};
-export const convertTimeArrayToString = (timeArray: number[], format: string = OUTPUT_TIME_FORMAT, needTime: boolean = false) => {
- let timeString = '';
+export const convertTimeArrayToString = (
+ timeArray: number[],
+ format: string = OUTPUT_TIME_FORMAT,
+ needTime: boolean = false,
+) => {
+ let timeString = "";
if (timeArray !== null && timeArray !== undefined) {
- const hour = timeArray[0] || 0;
- const minute = timeArray[1] || 0;
-
- timeString = dayjs()
- .set('hour', hour)
- .set('minute', minute)
- .set('second', 0)
- .format('HH:mm:ss');
+ const hour = timeArray[0] || 0;
+ const minute = timeArray[1] || 0;
+
+ timeString = dayjs()
+ .set("hour", hour)
+ .set("minute", minute)
+ .set("second", 0)
+ .format("HH:mm:ss");
}
-
- return timeString
-}
+
+ return timeString;
+};
const shortDateFormatter_en = new Intl.DateTimeFormat("en-HK", {
weekday: "short",
@@ -81,6 +92,36 @@ export const shortDateFormatter = (locale?: string) => {
}
};
+const clockFormatOptions: Intl.DateTimeFormatOptions = {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ weekday: "long",
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ hour12: true,
+};
+
+const clockTimeFormatter_en = new Intl.DateTimeFormat(
+ "en-HK",
+ clockFormatOptions,
+);
+const clockTimeformatter_zh = new Intl.DateTimeFormat(
+ "zh-HK",
+ clockFormatOptions,
+);
+
+export const clockTimeFormatter = (locale?: string) => {
+ switch (locale) {
+ case "zh":
+ return clockTimeformatter_zh;
+ case "en":
+ default:
+ return clockTimeFormatter_en;
+ }
+};
+
export function convertLocaleStringToNumber(numberString: string): number {
const numberWithoutCommas = numberString.replace(/,/g, "");
return parseFloat(numberWithoutCommas);
@@ -91,6 +132,6 @@ export function timestampToDateString(timestamp: string): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
- console.log(`${year}-${month}-${day}`)
+ console.log(`${year}-${month}-${day}`);
return `${year}-${month}-${day}`;
-}
\ No newline at end of file
+}
diff --git a/src/components/AppBar/Profile.tsx b/src/components/AppBar/Profile.tsx
index 7b48190..9fbe8e5 100644
--- a/src/components/AppBar/Profile.tsx
+++ b/src/components/AppBar/Profile.tsx
@@ -10,7 +10,7 @@ import Divider from "@mui/material/Divider";
import Typography from "@mui/material/Typography";
import { useTranslation } from "react-i18next";
import { signOut } from "next-auth/react";
-import { useRouter } from "next/navigation";
+import { usePathname, useRouter, useSearchParams } from "next/navigation";
type Props = Pick;
@@ -26,8 +26,17 @@ const Profile: React.FC = ({ avatarImageSrc, profileName }) => {
setProfileMenuAnchorEl(undefined);
};
- const { t } = useTranslation("login");
+ const { t, i18n: { language } } = useTranslation("login");
const router = useRouter();
+ const pathname = usePathname();
+ const searchParams = useSearchParams()
+
+ const onLangClick = React.useCallback((lang: string) => {
+ const params = new URLSearchParams(searchParams.toString())
+ params.set("lang", lang)
+ router.replace(`${pathname}?${params.toString()}`);
+ window.location.reload();
+ }, [router, pathname, searchParams]);
return (
<>
@@ -54,7 +63,9 @@ const Profile: React.FC = ({ avatarImageSrc, profileName }) => {
{profileName}
-
+
+ {language === "zh" && }
+ {language === "en" && }
>
diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx
index 1679019..516aca2 100644
--- a/src/components/Breadcrumb/Breadcrumb.tsx
+++ b/src/components/Breadcrumb/Breadcrumb.tsx
@@ -7,7 +7,7 @@ import MUILink from "@mui/material/Link";
import { usePathname } from "next/navigation";
import { useTranslation } from "react-i18next";
import Clock from "./Clock";
-import { Grid } from "@mui/material";
+import { Box, Grid } from "@mui/material";
import { I18nProvider } from "@/i18n";
const pathToLabelMap: { [path: string]: string } = {
@@ -46,42 +46,43 @@ const Breadcrumb = () => {
// const { t } = useTranslation("customer");
-
return (
-
-
-
- {segments.map((segment, index) => {
- const href = segments.slice(0, index + 1).join("/");
- const label = pathToLabelMap[href] || segment;
+
+
+ {segments.map((segment, index) => {
+ const href = segments.slice(0, index + 1).join("/");
+ const label = pathToLabelMap[href] || segment;
- if (index === segments.length - 1) {
- return (
-
- {label}
- {/* {t(label)} */}
-
- );
- } else {
- return (
-
- {label}
-
- );
- }
- })}
-
-
-
-
-
-
+ if (index === segments.length - 1) {
+ return (
+
+ {label}
+ {/* {t(label)} */}
+
+ );
+ } else {
+ return (
+
+ {label}
+
+ );
+ }
+ })}
+
+
+
+
+
);
};
diff --git a/src/components/Breadcrumb/Clock.tsx b/src/components/Breadcrumb/Clock.tsx
index 8ddf42b..9e34340 100644
--- a/src/components/Breadcrumb/Clock.tsx
+++ b/src/components/Breadcrumb/Clock.tsx
@@ -1,32 +1,33 @@
-"use client"
-import { useState, useEffect, useLayoutEffect } from 'react';
-import Typography from "@mui/material/Typography";
-import { useTranslation } from 'react-i18next';
+"use client";
+import React, { useState, useLayoutEffect } from "react";
+import Typography, { TypographyProps } from "@mui/material/Typography";
+import { useTranslation } from "react-i18next";
+import { clockTimeFormatter } from "@/app/utils/formatUtil";
+import { NoSsr } from "@mui/material";
-const Clock = () => {
- const {
- i18n: { language },
- } = useTranslation();
- const [currentDateTime, setCurrentDateTime] = useState(new Date());
+const Clock: React.FC = (props) => {
+ const {
+ i18n: { language },
+ } = useTranslation();
+ const [currentDateTime, setCurrentDateTime] = useState(new Date());
- useLayoutEffect(() => {
- const timer = setInterval(() => {
- setCurrentDateTime(new Date());
- }, 1000);
+ useLayoutEffect(() => {
+ const timer = setInterval(() => {
+ setCurrentDateTime(new Date());
+ }, 1000);
- return () => {
- clearInterval(timer);
- };
- }, []);
+ return () => {
+ clearInterval(timer);
+ };
+ }, []);
- const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true };
- const formattedDateTime = new Intl.DateTimeFormat(language, options).format(currentDateTime)
-
- return (
-
- {formattedDateTime}
-
- );
+ return (
+
+
+ {clockTimeFormatter(language).format(currentDateTime)}
+
+
+ );
};
export default Clock;
diff --git a/src/components/ChangePassword/ChangePassword.tsx b/src/components/ChangePassword/ChangePassword.tsx
index 1fc384f..01dedcb 100644
--- a/src/components/ChangePassword/ChangePassword.tsx
+++ b/src/components/ChangePassword/ChangePassword.tsx
@@ -8,7 +8,6 @@ import { useTranslation } from "react-i18next";
import { Button, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material";
import { Check, Close, Error } from "@mui/icons-material";
import ChagnePasswordForm from "./ChangePasswordForm";
-import { ServerFetchError } from "@/app/utils/fetchUtil";
// interface Props {
// // auth?: auth[]
diff --git a/src/components/CreateProject/CreateProject.tsx b/src/components/CreateProject/CreateProject.tsx
index c4aa9d5..4242afb 100644
--- a/src/components/CreateProject/CreateProject.tsx
+++ b/src/components/CreateProject/CreateProject.tsx
@@ -76,7 +76,7 @@ const hasErrorsInTab = (
switch (tabIndex) {
case 0:
return (
- errors.projectName || errors.projectCode || errors.projectDescription
+ errors.projectName || errors.projectDescription || errors.clientId
);
case 2:
return (
@@ -219,6 +219,7 @@ const CreateProject: React.FC = ({
data.projectActualEnd = dayjs().format("YYYY-MM-DD");
}
+ data.taskTemplateId = data.taskTemplateId === "All" ? undefined : data.taskTemplateId;
const response = await saveProject(data);
if (response.id > 0) {
@@ -248,7 +249,8 @@ const CreateProject: React.FC = ({
if (
errors.projectName ||
errors.projectDescription ||
- errors.projectCode
+ // errors.projectCode ||
+ errors.clientId
) {
setTabIndex(0);
} else if (errors.totalManhour || errors.manhourPercentageByGrade || errors.taskGroups) {
@@ -266,6 +268,7 @@ const CreateProject: React.FC = ({
allocatedStaffIds: [],
milestones: {},
totalManhour: 0,
+ taskTemplateId: "All",
...defaultInputs,
// manhourPercentageByGrade should have a sensible default
@@ -289,7 +292,8 @@ const CreateProject: React.FC = ({
>
{isEditMode && !(formProps.getValues("projectDeleted") === true) && (
- {!formProps.getValues("projectActualStart") && (
+ {/* {!formProps.getValues("projectActualStart") && ( */}
+ {formProps.getValues("projectStatus") === "Pending to Start" && (
)}
- {formProps.getValues("projectActualStart") &&
- !formProps.getValues("projectActualEnd") && (
+ {/* {formProps.getValues("projectActualStart") &&
+ !formProps.getValues("projectActualEnd") && ( */}
+ {formProps.getValues("projectStatus") === "On-going" && (
)}
{!(
- formProps.getValues("projectActualStart") &&
- formProps.getValues("projectActualEnd")
+ // formProps.getValues("projectActualStart") &&
+ // formProps.getValues("projectActualEnd")
+ formProps.getValues("projectStatus") === "Completed" ||
+ formProps.getValues("projectStatus") === "Deleted"
) && (
-
+
{t("Client")}
(
diff --git a/src/components/CreateProject/TaskSetup.tsx b/src/components/CreateProject/TaskSetup.tsx
index d24da23..dac0698 100644
--- a/src/components/CreateProject/TaskSetup.tsx
+++ b/src/components/CreateProject/TaskSetup.tsx
@@ -33,7 +33,7 @@ const TaskSetup: React.FC = ({
isActive,
}) => {
const { t } = useTranslation();
- const { setValue, watch, clearErrors, setError } = useFormContext();
+ const { setValue, watch, clearErrors, setError, formState: { defaultValues } } = useFormContext();
const currentTaskGroups = watch("taskGroups");
const currentTaskIds = Object.values(currentTaskGroups).reduce(
(acc, group) => {
@@ -48,7 +48,7 @@ const TaskSetup: React.FC = ({
const [selectedTaskTemplateId, setSelectedTaskTemplateId] = useState<
"All" | number
- >("All");
+ >(watch("taskTemplateId") ?? "All");
const onSelectTaskTemplate = useCallback(
(e: SelectChangeEvent) => {
if (e.target.value === "All" || isNumber(e.target.value)) {
@@ -64,7 +64,8 @@ const TaskSetup: React.FC = ({
(template) => template.id === selectedTaskTemplateId,
)
- if (selectedTaskTemplateId !== "All") {
+ if (selectedTaskTemplateId !== "All" && selectedTaskTemplateId !== watch("taskTemplateId")) {
+
// update the "manhour allocation by grade" by task template
const updatedManhourPercentageByGrade: ManhourAllocation = watch("manhourPercentageByGrade")
selectedTaskTemplate?.gradeAllocations.forEach((gradeAllocation) => {
@@ -73,28 +74,30 @@ const TaskSetup: React.FC = ({
setValue("manhourPercentageByGrade", updatedManhourPercentageByGrade)
if (Object.values(updatedManhourPercentageByGrade).reduce((acc, value) => acc + value, 0) === 100) clearErrors("manhourPercentageByGrade")
- else setError("manhourPercentageByGrade", {message: "manhourPercentageByGrade value is not valid", type: "invalid"})
+ else setError("manhourPercentageByGrade", { message: "manhourPercentageByGrade value is not valid", type: "invalid" })
// update the "manhour allocation by grade by stage" by task template
const updatedTaskGroups = watch("taskGroups")
const taskGroupsKeys = Object.keys(updatedTaskGroups)
selectedTaskTemplate?.groupAllocations.forEach((groupAllocation) => {
const taskGroupId = groupAllocation.taskGroup.id
- if(taskGroupsKeys.includes(taskGroupId.toString())) {
- updatedTaskGroups[taskGroupId] = {...updatedTaskGroups[taskGroupId], percentAllocation: groupAllocation?.percentage}
+ if (taskGroupsKeys.includes(taskGroupId.toString())) {
+ updatedTaskGroups[taskGroupId] = { ...updatedTaskGroups[taskGroupId], percentAllocation: groupAllocation?.percentage }
}
})
const percentageToZeroGroupIds = difference(taskGroupsKeys.map(key => parseFloat(key)), selectedTaskTemplate?.groupAllocations.map(groupAllocation => groupAllocation.taskGroup.id)!!)
percentageToZeroGroupIds.forEach((percentageToZeroGroupId) => {
- updatedTaskGroups[percentageToZeroGroupId] = {...updatedTaskGroups[percentageToZeroGroupId], percentAllocation: 0}
+ updatedTaskGroups[percentageToZeroGroupId] = { ...updatedTaskGroups[percentageToZeroGroupId], percentAllocation: 0 }
})
-
+
setValue("taskGroups", updatedTaskGroups)
if (Object.values(updatedTaskGroups).reduce((acc, value) => acc + value.percentAllocation, 0) === 100) clearErrors("taskGroups")
- else setError("taskGroups", {message: "Task Groups value is not invalid", type: "invalid"})
+ else setError("taskGroups", { message: "Task Groups value is not invalid", type: "invalid" })
}
+ setValue("taskTemplateId", selectedTaskTemplateId)
+
const taskList =
selectedTaskTemplateId === "All"
? tasks
@@ -176,7 +179,26 @@ const TaskSetup: React.FC = ({
};
}, {});
- setValue("taskGroups", newTaskGroups);
+ // update the "manhour allocation by grade by stage" by task template
+ const taskGroupsKeys = Object.keys(newTaskGroups)
+ const selectedTaskTemplate = taskTemplates.find(
+ (template) => template.id === selectedTaskTemplateId,
+ )
+ selectedTaskTemplate?.groupAllocations.forEach((groupAllocation) => {
+ const taskGroupId = groupAllocation.taskGroup.id
+ if (taskGroupsKeys.includes(taskGroupId.toString())) {
+ newTaskGroups[taskGroupId] = { ...newTaskGroups[taskGroupId], percentAllocation: groupAllocation?.percentage }
+ }
+ })
+
+ const percentageToZeroGroupIds = difference(taskGroupsKeys.map(key => parseFloat(key)), selectedTaskTemplate?.groupAllocations.map(groupAllocation => groupAllocation.taskGroup.id)!!)
+ percentageToZeroGroupIds.forEach((percentageToZeroGroupId) => {
+ newTaskGroups[percentageToZeroGroupId] = { ...newTaskGroups[percentageToZeroGroupId], percentAllocation: 0 }
+ })
+
+ setValue("taskGroups", newTaskGroups)
+ if (Object.values(newTaskGroups).reduce((acc, value) => acc + value.percentAllocation, 0) === 100) clearErrors("taskGroups")
+ else setError("taskGroups", { message: "Task Groups value is not invalid", type: "invalid" })
}}
allItemsLabel={t("Task Pool")}
selectedItemsLabel={t("Project Task List")}
diff --git a/src/components/CreateSkill/CreateSkill.tsx b/src/components/CreateSkill/CreateSkill.tsx
index d264b34..cb2acc7 100644
--- a/src/components/CreateSkill/CreateSkill.tsx
+++ b/src/components/CreateSkill/CreateSkill.tsx
@@ -23,7 +23,7 @@ const CreateSkill: React.FC = () => {
const [serverError, setServerError] = useState("");
const router = useRouter();
const { t } = useTranslation();
- const [tabIndex, setTabIndex] = useState(0);
+ // const [tabIndex, setTabIndex] = useState(0);
const errors = formProps.formState.errors;
const onSubmit = useCallback>(
@@ -44,28 +44,28 @@ const CreateSkill: React.FC = () => {
router.back();
};
-// const handleReset = useCallback(() => {
-// console.log(defaultValues)
-// }, [defaultValues])
+ const resetSkill = useCallback(() => {
+ formProps.reset()
+}, [])
- const handleTabChange = useCallback>(
- (_e, newValue) => {
- setTabIndex(newValue);
- },
- []
- );
+ // const handleTabChange = useCallback>(
+ // (_e, newValue) => {
+ // setTabIndex(newValue);
+ // },
+ // []
+ // );
- const hasErrorsInTab = (
- tabIndex: number,
- errors: FieldErrors
- ) => {
- switch (tabIndex) {
- case 0:
- return Object.keys(errors).length > 0;
- default:
- false;
- }
- };
+ // const hasErrorsInTab = (
+ // tabIndex: number,
+ // errors: FieldErrors
+ // ) => {
+ // switch (tabIndex) {
+ // case 0:
+ // return Object.keys(errors).length > 0;
+ // default:
+ // false;
+ // }
+ // };
return (
<>
@@ -74,13 +74,13 @@ const CreateSkill: React.FC = () => {
component="form"
onSubmit={formProps.handleSubmit(onSubmit)}
>
-
@@ -88,15 +88,22 @@ const CreateSkill: React.FC = () => {
}
iconPosition="end"
/>
- {/* */}
-
+ */}
{serverError && (
{serverError}
)}
- {tabIndex === 0 && }
+ {/* {tabIndex === 0 && } */}
+
+ }
+ onClick={resetSkill}
+ >
+ {t("Reset")}
+
}
diff --git a/src/components/CreateSkill/SkillInfo.tsx b/src/components/CreateSkill/SkillInfo.tsx
index be9724d..ce0e07e 100644
--- a/src/components/CreateSkill/SkillInfo.tsx
+++ b/src/components/CreateSkill/SkillInfo.tsx
@@ -15,8 +15,7 @@ import { FormControl, InputLabel, MenuItem, Select } from "@mui/material";
import { useCallback } from "react";
import { CreateSkillInputs } from "@/app/api/skill/actions";
-const SkillInfo: React.FC = (
-) => {
+const SkillInfo: React.FC = () => {
const { t } = useTranslation();
const {
register,
@@ -27,13 +26,6 @@ const SkillInfo: React.FC = (
setValue,
} = useFormContext();
- const resetSkill = useCallback(() => {
- console.log(defaultValues);
- if (defaultValues !== undefined) {
- resetField("name");
- }
- }, [defaultValues]);
-
return (
<>
@@ -42,45 +34,61 @@ const SkillInfo: React.FC = (
{t("Skill Info")}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/CreateStaff/CreateStaff.tsx b/src/components/CreateStaff/CreateStaff.tsx
index 21904d0..b8d46cf 100644
--- a/src/components/CreateStaff/CreateStaff.tsx
+++ b/src/components/CreateStaff/CreateStaff.tsx
@@ -22,6 +22,7 @@ import { fetchSkillCombo } from "@/app/api/skill/actions";
import { fetchSalaryCombo } from "@/app/api/salarys/actions";
import StaffInfo from "./StaffInfo";
import { Check, Close } from "@mui/icons-material";
+import { ServerFetchError } from "@/app/utils/fetchUtil";
interface Field {
id: string;
@@ -59,6 +60,10 @@ const CreateStaff: React.FC = ({ combos }) => {
const errors = formProps.formState.errors;
+ const checkDuplicates = (str1: string, str2: string, str3: string) => {
+ return str1 === str2 || str1 === str3 || str2 === str3;
+ }
+
const onSubmit = useCallback>(
async (data) => {
try {
@@ -85,6 +90,14 @@ const CreateStaff: React.FC = ({ combos }) => {
formProps.setError("phone2", { message: t("Please Enter Correct Phone No.."), type: "required" })
}
}
+ if (data.phone1 === data.phone2 || data.phone1 === data.emergContactPhone || data.phone2 === data.emergContactPhone) {
+ haveError = true
+ formProps.setError("phone1", { message: t("Please Enter Different Phone No.."), type: "required" })
+ if (data.phone2!.length > 0) {
+ formProps.setError("phone2", { message: t("Please Enter Different Phone No.."), type: "required" })
+ }
+ formProps.setError("emergContactPhone", { message: t("Please Enter Different Phone No.."), type: "required" })
+ }
if (!regex_email.test(data.email)) {
haveError = true
formProps.setError("email", { message: t("Please Enter Correct Email."), type: "required" })
@@ -93,10 +106,6 @@ const CreateStaff: React.FC = ({ combos }) => {
haveError = true
formProps.setError("companyId", { message: t("Please Enter Company."), type: "required" })
}
- if (!data.gradeId) {
- haveError = true
- formProps.setError("gradeId", { message: t("Please Enter grade."), type: "required" })
- }
if (!data.employType) {
haveError = true
formProps.setError("employType", { message: t("Please Enter Employ Type."), type: "required" })
@@ -117,19 +126,20 @@ const CreateStaff: React.FC = ({ combos }) => {
haveError = true
formProps.setError("departDate", { message: t("Depart Date cannot be earlier than Join Date."), type: "required" })
}
- // if (!data.joinPositionId) {
- // haveError = true
- // formProps.setError("joinPositionId", { message: t("Depart Date cannot be earlier than Join Date."), type: "required" })
- // }
if (haveError) {
return
}
console.log("passed")
await saveStaff(data)
router.replace("/settings/staff")
- } catch (e) {
+ } catch (e: any) {
console.log(e);
- setServerError(t("An error has occurred. Please try again later."));
+ formProps.setError("staffId", { message: t("Please Enter Employ Type."), type: "required" })
+ let msg = ""
+ if (e.message === "Duplicated StaffId Found") {
+ msg = t("Duplicated StaffId Found")
+ }
+ setServerError(`${t("An error has occurred. Please try again later.")} ${msg} `);
}
},
[router]
diff --git a/src/components/CreateStaff/StaffInfo.tsx b/src/components/CreateStaff/StaffInfo.tsx
index 333be87..30aca1a 100644
--- a/src/components/CreateStaff/StaffInfo.tsx
+++ b/src/components/CreateStaff/StaffInfo.tsx
@@ -164,7 +164,7 @@ const StaffInfo: React.FC = ({ combos }) => {
- {t("Department")}
+ {t("Department")}
= ({ combos }) => {
- {t("Grade")}
+ {t("Grade")}
= ({ combos }) => {
- {t("Salary Point")}
+ {t("Salary Point")}
= ({ combos }) => {
- {t("Employ Type")}
+ {t("Employ Type")}
= ({ combos }) => {
+ >
{
- if (!date) return;
- setValue("joinDate", date.format(INPUT_DATE_FORMAT));
- }}
- slotProps={{
- textField: {
- error:
- joinDate === "Invalid Date" || Boolean(errors.joinDate),
- // value: errors.joinDate?.message,
- },
- }}
- />
+ if (!date) return;
+ setValue("joinDate", date.format(INPUT_DATE_FORMAT));
+ }}
+ slotProps={{
+ textField: {
+ required: true,
+ error:
+ joinDate === "Invalid Date" || Boolean(errors.joinDate),
+ // value: errors.joinDate?.message,
+ },
+ }}
+ />
- {t("Join Position")}
+ {t("Join Position")}
= ({ combos }) => {
{combos.position.map((position, index) => (
diff --git a/src/components/CreateTeam/CreateTeam.tsx b/src/components/CreateTeam/CreateTeam.tsx
index 64159c0..107b939 100644
--- a/src/components/CreateTeam/CreateTeam.tsx
+++ b/src/components/CreateTeam/CreateTeam.tsx
@@ -1,5 +1,4 @@
"use client";
-
import {
FieldErrors,
FormProvider,
@@ -13,7 +12,7 @@ import { CreateTeamInputs, saveTeam } from "@/app/api/team/actions";
import { Button, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material";
import { Check, Close } from "@mui/icons-material";
import { useCallback, useState } from "react";
-import { useRouter, useSearchParams } from "next/navigation";
+import { useRouter } from "next/navigation";
import { useTranslation } from "react-i18next";
import { Error } from "@mui/icons-material";
import TeamInfo from "./TeamInfo";
@@ -28,7 +27,7 @@ const CreateTeam: React.FC = ({ allstaff }) => {
const router = useRouter();
const [tabIndex, setTabIndex] = useState(0);
const { t } = useTranslation();
- const searchParams = useSearchParams()
+ // const searchParams = useSearchParams()
const errors = formProps.formState.errors;
@@ -81,7 +80,7 @@ const hasErrorsInTab = (
variant="scrollable"
>
@@ -98,8 +97,6 @@ const hasErrorsInTab = (
)}
{tabIndex === 0 && }
{tabIndex === 1 && }
-
- {/* */}
diff --git a/src/components/EditSkill/EditSkillForm.tsx b/src/components/EditSkill/EditSkillForm.tsx
index 120d2e5..50f813b 100644
--- a/src/components/EditSkill/EditSkillForm.tsx
+++ b/src/components/EditSkill/EditSkillForm.tsx
@@ -1,47 +1,30 @@
"use client";
-
-import { CreateSkillInputs } from "@/app/api/skill/actions";
-import {
- Box,
- Button,
- Card,
- CardContent,
- Grid,
- Stack,
- Tab,
- Tabs,
- TabsProps,
- TextField,
- Typography,
-} from "@mui/material";
-import { useSearchParams } from "next/navigation";
-import {
- FieldErrors,
- FormProvider,
- SubmitErrorHandler,
- SubmitHandler,
- useForm,
- useFormContext,
-} from "react-hook-form";
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import Card from "@mui/material/Card";
+import CardContent from "@mui/material/CardContent";
+import Grid from "@mui/material/Grid";
+import TextField from "@mui/material/TextField";
+import Typography from "@mui/material/Typography";
import { useTranslation } from "react-i18next";
+import CardActions from "@mui/material/CardActions";
+import RestartAlt from "@mui/icons-material/RestartAlt";
+import Button from "@mui/material/Button";
+import { Controller, useFormContext } from "react-hook-form";
+import { FormControl, InputLabel, MenuItem, Select } from "@mui/material";
+import { useCallback } from "react";
+import { CreateSkillInputs } from "@/app/api/skill/actions";
-interface Props {
- // users: UserResult[]
-}
-
-const EditSkillForm: React.FC = async ({}) => {
+const EditSkillForm: React.FC = () => {
const { t } = useTranslation();
- const searchParams = useSearchParams();
- const idString = searchParams.get("id");
const {
register,
- setValue,
- getValues,
formState: { errors, defaultValues },
+ control,
reset,
resetField,
+ setValue,
} = useFormContext();
- // const formProps = useForm({});
return (
<>
@@ -65,13 +48,14 @@ const EditSkillForm: React.FC = async ({}) => {
Boolean(errors.name) &&
(errors.name?.message
? t(errors.name.message)
- : t("Please input correct name"))
- }
+ : `${t("Please input correct ")}${t("name")}`
+
+ )}
/>
= async ({}) => {
Boolean(errors.code) &&
(errors.code?.message
? t(errors.code.message)
- : t("Please input correct name"))
- }
+ : `${t("Please input correct ")}${t("code")}`
+ )}
/>
= async ({}) => {
Boolean(errors.description) &&
(errors.description?.message
? t(errors.description.message)
- : t("Please input correct name"))
- }
+ : `${t("Please input correct ")}${t("description")}`
+ )}
/>
diff --git a/src/components/EditTeam/Allocation.tsx b/src/components/EditTeam/Allocation.tsx
index b732168..2376ece 100644
--- a/src/components/EditTeam/Allocation.tsx
+++ b/src/components/EditTeam/Allocation.tsx
@@ -127,14 +127,14 @@ const Allocation: React.FC = ({ allStaffs: staff, teamLead }) => {
const StaffPoolColumns = useMemo[]>(
() => [
{
- label: t("Add"),
+ label: t("add"),
name: "id",
onClick: addStaff,
buttonIcon: ,
},
{ label: t("Staff Id"), name: "staffId" },
{ label: t("Staff Name"), name: "name" },
- { label: t("Current Position"), name: "currentPosition" },
+ { label: t("Position"), name: "currentPosition" },
],
[addStaff, t]
);
@@ -142,16 +142,16 @@ const Allocation: React.FC = ({ allStaffs: staff, teamLead }) => {
const allocatedStaffColumns = useMemo[]>(
() => [
{
- label: t("Remove"),
+ label: t("remove"),
name: "action",
onClick: removeStaff,
buttonIcon: ,
},
{ label: t("Staff Id"), name: "staffId" },
{ label: t("Staff Name"), name: "name" },
- { label: t("Current Position"), name: "currentPosition" },
+ { label: t("Position"), name: "currentPosition" },
{
- label: t("Team Lead"),
+ label: t("teamLead"),
name: "action",
onClick: setTeamLead,
buttonIcon: ,
@@ -210,9 +210,6 @@ const Allocation: React.FC = ({ allStaffs: staff, teamLead }) => {
sx={{ display: "flex", flexDirection: "column", gap: 1 }}
>
-
- {t("staff")}
-
@@ -221,7 +218,7 @@ const Allocation: React.FC = ({ allStaffs: staff, teamLead }) => {
fullWidth
onChange={onQueryInputChange}
value={query}
- placeholder={t("Search by staff ID, name or position.")}
+ placeholder={t("Search by Staff Id, Name or Position.")}
InputProps={{
endAdornment: query && (
diff --git a/src/components/EditTeam/EditTeam.tsx b/src/components/EditTeam/EditTeam.tsx
index 432bc0e..cd5f83a 100644
--- a/src/components/EditTeam/EditTeam.tsx
+++ b/src/components/EditTeam/EditTeam.tsx
@@ -68,15 +68,13 @@ const EditTeam: React.FC = async ({ staff, desc }) => {
);
useEffect(() => {
let idList: number[] = []
- console.log(desc)
+ // console.log(desc)
if (idString) {
const filteredTeam = staff.filter(
(item) => {
- console.log(item)
- console.log(parseInt(idString))
return (item.teamId === parseInt(idString))}
);
- console.log(filteredTeam)
+ // console.log(filteredTeam)
const tempDesc = desc.filter(
(item) => item.id === parseInt(idString)
)
@@ -100,15 +98,15 @@ const EditTeam: React.FC = async ({ staff, desc }) => {
// }
idList = filteredIds
- console.log(filteredIds)
+ // console.log(filteredIds)
}
- console.log(idList)
+ // console.log(idList)
setFilteredItems(filteredTeam);
formProps.reset({description: tempDesc[0].description, addStaffIds: idList})
setFilteredDesc(tempDesc[0].description)
setFilteredName(tempDesc[0].name)
}
- console.log(staff)
+ // console.log(staff)
setAllStaffs(staff)
@@ -133,7 +131,7 @@ const EditTeam: React.FC = async ({ staff, desc }) => {
const onSubmit = useCallback>(
async (data) => {
try {
- console.log(data);
+ // console.log(data);
const tempData = {
description: data.description,
addStaffIds: data.addStaffIds,
diff --git a/src/components/PastEntryCalendar/PastEntryCalendar.tsx b/src/components/PastEntryCalendar/PastEntryCalendar.tsx
new file mode 100644
index 0000000..a9e6a14
--- /dev/null
+++ b/src/components/PastEntryCalendar/PastEntryCalendar.tsx
@@ -0,0 +1,106 @@
+import React from "react";
+import {
+ RecordTimesheetInput,
+ RecordLeaveInput,
+} from "@/app/api/timesheets/actions";
+import {
+ DateCalendar,
+ LocalizationProvider,
+ PickersDay,
+ PickersDayProps,
+} from "@mui/x-date-pickers";
+import { useTranslation } from "react-i18next";
+import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
+import dayjs, { Dayjs } from "dayjs";
+import "dayjs/locale/zh-hk";
+import timezone from "dayjs/plugin/timezone";
+import utc from "dayjs/plugin/utc";
+import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
+
+dayjs.extend(utc);
+dayjs.extend(timezone);
+
+dayjs.tz.guess();
+
+export interface Props {
+ timesheet: RecordTimesheetInput;
+ leaves: RecordLeaveInput;
+ onDateSelect: (date: string) => void;
+}
+
+const getColor = (
+ hasTimeInput: boolean,
+ hasLeave: boolean,
+): string | undefined => {
+ if (hasTimeInput && hasLeave) {
+ return "success.light";
+ } else if (hasTimeInput) {
+ return "info.light";
+ } else if (hasLeave) {
+ return "warning.light";
+ } else {
+ return undefined;
+ }
+};
+
+const EntryDay: React.FC & Props> = ({
+ timesheet,
+ leaves,
+ ...pickerProps
+}) => {
+ const timesheetDays = Object.keys(timesheet);
+ const leaveDays = Object.keys(leaves);
+
+ const hasTimesheetInput = timesheetDays.some((day) =>
+ dayjs(day).isSame(pickerProps.day, "day"),
+ );
+
+ const hasLeaveInput = leaveDays.some((day) =>
+ dayjs(day).isSame(pickerProps.day, "day"),
+ );
+
+ return (
+
+ );
+};
+
+const PastEntryCalendar: React.FC = ({
+ timesheet,
+ leaves,
+ onDateSelect,
+}) => {
+ const {
+ i18n: { language },
+ } = useTranslation("home");
+
+ const onChange = (day: Dayjs) => {
+ onDateSelect(day.format(INPUT_DATE_FORMAT));
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default PastEntryCalendar;
diff --git a/src/components/PastEntryCalendar/PastEntryCalendarModal.tsx b/src/components/PastEntryCalendar/PastEntryCalendarModal.tsx
new file mode 100644
index 0000000..53fd8dc
--- /dev/null
+++ b/src/components/PastEntryCalendar/PastEntryCalendarModal.tsx
@@ -0,0 +1,99 @@
+import {
+ Box,
+ Button,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Stack,
+ Typography,
+ styled,
+} from "@mui/material";
+import PastEntryCalendar, {
+ Props as PastEntryCalendarProps,
+} from "./PastEntryCalendar";
+import { useCallback, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { ArrowBack } from "@mui/icons-material";
+
+interface Props extends Omit {
+ open: boolean;
+ handleClose: () => void;
+}
+
+const Indicator = styled(Box)(() => ({
+ borderRadius: "50%",
+ width: "1rem",
+ height: "1rem",
+}));
+
+const PastEntryCalendarModal: React.FC = ({
+ handleClose,
+ open,
+ timesheet,
+ leaves,
+}) => {
+ const { t } = useTranslation("home");
+
+ const [selectedDate, setSelectedDate] = useState("");
+
+ const clearDate = useCallback(() => {
+ setSelectedDate("");
+ }, []);
+
+ const onClose = useCallback(() => {
+ handleClose();
+ }, [handleClose]);
+
+ return (
+
+ );
+};
+
+export default PastEntryCalendarModal;
diff --git a/src/components/PastEntryCalendar/index.ts b/src/components/PastEntryCalendar/index.ts
new file mode 100644
index 0000000..1c7ee56
--- /dev/null
+++ b/src/components/PastEntryCalendar/index.ts
@@ -0,0 +1 @@
+export { default } from "./PastEntryCalendar";
diff --git a/src/components/ProjectSearch/ProjectSearch.tsx b/src/components/ProjectSearch/ProjectSearch.tsx
index 55b4cc7..79ee51b 100644
--- a/src/components/ProjectSearch/ProjectSearch.tsx
+++ b/src/components/ProjectSearch/ProjectSearch.tsx
@@ -46,6 +46,12 @@ const ProjectSearch: React.FC = ({ projects, projectCategories }) => {
type: "select",
options: uniq(projects.map((project) => project.team)),
},
+ {
+ label: t("Status"),
+ paramName: "status",
+ type: "select",
+ options: uniq(projects.map((project) => project.status)),
+ },
],
[t, projectCategories, projects],
);
@@ -74,6 +80,7 @@ const ProjectSearch: React.FC = ({ projects, projectCategories }) => {
{ name: "category", label: t("Project Category") },
{ name: "team", label: t("Team") },
{ name: "client", label: t("Client") },
+ { name: "status", label: t("Status") },
],
[t, onProjectClick],
);
@@ -90,7 +97,8 @@ const ProjectSearch: React.FC = ({ projects, projectCategories }) => {
p.name.toLowerCase().includes(query.name.toLowerCase()) &&
(query.client === "All" || p.client === query.client) &&
(query.category === "All" || p.category === query.category) &&
- (query.team === "All" || p.team === query.team),
+ (query.team === "All" || p.team === query.team) &&
+ (query.status === "All" || p.status === query.status),
),
);
}}
diff --git a/src/components/Report/ReportSearchBox/SearchBox.tsx b/src/components/Report/ReportSearchBox/SearchBox.tsx
index 463aa0c..637d6f2 100644
--- a/src/components/Report/ReportSearchBox/SearchBox.tsx
+++ b/src/components/Report/ReportSearchBox/SearchBox.tsx
@@ -113,113 +113,135 @@ function SearchBox({
onSearch(inputs);
};
-
const handleDownload = async () => {
- //setIsLoading(true);
-
try {
- const response = await fetch('/temp/AR01_Late Start Report.xlsx', {
+ const response = await fetch('/api/reports', {
+ method: 'POST',
headers: {
- 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'Content-Type': 'application/json',
},
+ body: JSON.stringify({ projectId: '123' }), // Example payload
});
if (!response.ok) throw new Error('Network response was not ok.');
-
+
const data = await response.blob();
- const reader = new FileReader();
- reader.onload = (e) => {
- if (e.target && e.target.result) {
- const ab = e.target.result as ArrayBuffer;
- const workbook = XLSX.read(ab, { type: 'array' });
- const firstSheetName = workbook.SheetNames[0];
- const worksheet = workbook.Sheets[firstSheetName];
+ const url = window.URL.createObjectURL(data);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = "Project_Cash_Flow_Report.xlsx";
+ document.body.appendChild(a);
+ a.click();
+ a.remove();
+ } catch (error) {
+ console.error('Error downloading the file: ', error);
+ }
+ };
+ // const handleDownload = async () => {
+ // //setIsLoading(true);
+
+ // try {
+ // const response = await fetch('/temp/AR01_Late Start Report.xlsx', {
+ // headers: {
+ // 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ // },
+ // });
+ // if (!response.ok) throw new Error('Network response was not ok.');
+
+ // const data = await response.blob();
+ // const reader = new FileReader();
+ // reader.onload = (e) => {
+ // if (e.target && e.target.result) {
+ // const ab = e.target.result as ArrayBuffer;
+ // const workbook = XLSX.read(ab, { type: 'array' });
+ // const firstSheetName = workbook.SheetNames[0];
+ // const worksheet = workbook.Sheets[firstSheetName];
- // Add the current date to cell C2
- const cellAddress = 'C2';
- const date = new Date().toISOString().split('T')[0]; // Format YYYY-MM-DD
- const formattedDate = date.replace(/-/g, '/'); // Change format to YYYY/MM/DD
- XLSX.utils.sheet_add_aoa(worksheet, [[formattedDate]], { origin: cellAddress });
+ // // Add the current date to cell C2
+ // const cellAddress = 'C2';
+ // const date = new Date().toISOString().split('T')[0]; // Format YYYY-MM-DD
+ // const formattedDate = date.replace(/-/g, '/'); // Change format to YYYY/MM/DD
+ // XLSX.utils.sheet_add_aoa(worksheet, [[formattedDate]], { origin: cellAddress });
- // Style for cell A1: Font size 16 and bold
- if (worksheet['A1']) {
- worksheet['A1'].s = {
- font: {
- bold: true,
- sz: 16, // Font size 16
- //name: 'Times New Roman' // Specify font
- }
- };
- }
+ // // Style for cell A1: Font size 16 and bold
+ // if (worksheet['A1']) {
+ // worksheet['A1'].s = {
+ // font: {
+ // bold: true,
+ // sz: 16, // Font size 16
+ // //name: 'Times New Roman' // Specify font
+ // }
+ // };
+ // }
- // Apply styles from A2 to A4 (bold)
- ['A2', 'A3', 'A4'].forEach(cell => {
- if (worksheet[cell]) {
- worksheet[cell].s = { font: { bold: true } };
- }
- });
+ // // Apply styles from A2 to A4 (bold)
+ // ['A2', 'A3', 'A4'].forEach(cell => {
+ // if (worksheet[cell]) {
+ // worksheet[cell].s = { font: { bold: true } };
+ // }
+ // });
- // Formatting from A6 to J6
- // Apply styles from A6 to J6 (bold, bottom border, center alignment)
- for (let col = 0; col < 10; col++) { // Columns A to J
- const cellRef = XLSX.utils.encode_col(col) + '6';
- if (worksheet[cellRef]) {
- worksheet[cellRef].s = {
- font: { bold: true },
- alignment: { horizontal: 'center' },
- border: {
- bottom: { style: 'thin', color: { auto: 1 } }
- }
- };
- }
- }
+ // // Formatting from A6 to J6
+ // // Apply styles from A6 to J6 (bold, bottom border, center alignment)
+ // for (let col = 0; col < 10; col++) { // Columns A to J
+ // const cellRef = XLSX.utils.encode_col(col) + '6';
+ // if (worksheet[cellRef]) {
+ // worksheet[cellRef].s = {
+ // font: { bold: true },
+ // alignment: { horizontal: 'center' },
+ // border: {
+ // bottom: { style: 'thin', color: { auto: 1 } }
+ // }
+ // };
+ // }
+ // }
- const firstTableData = [
- ['Column1', 'Column2', 'Column3'], // Row 1
- ['Data1', 'Data2', 'Data3'], // Row 2
- // ... more rows as needed
- ];
- // Find the last row of the first table
- let lastRowOfFirstTable = 6; // Starting row for data in the first table
- while (worksheet[XLSX.utils.encode_cell({ c: 0, r: lastRowOfFirstTable })]) {
- lastRowOfFirstTable++;
- }
+ // const firstTableData = [
+ // ['Column1', 'Column2', 'Column3'], // Row 1
+ // ['Data1', 'Data2', 'Data3'], // Row 2
+ // // ... more rows as needed
+ // ];
+ // // Find the last row of the first table
+ // let lastRowOfFirstTable = 6; // Starting row for data in the first table
+ // while (worksheet[XLSX.utils.encode_cell({ c: 0, r: lastRowOfFirstTable })]) {
+ // lastRowOfFirstTable++;
+ // }
- // Calculate the maximum length of content in each column and set column width
- const colWidths: number[] = [];
+ // // Calculate the maximum length of content in each column and set column width
+ // const colWidths: number[] = [];
- const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: "", blankrows: true }) as (string | number)[][];
- jsonData.forEach((row: (string | number)[]) => {
- row.forEach((cell: string | number, index: number) => {
- const valueLength = cell.toString().length;
- colWidths[index] = Math.max(colWidths[index] || 0, valueLength);
- });
- });
+ // const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: "", blankrows: true }) as (string | number)[][];
+ // jsonData.forEach((row: (string | number)[]) => {
+ // row.forEach((cell: string | number, index: number) => {
+ // const valueLength = cell.toString().length;
+ // colWidths[index] = Math.max(colWidths[index] || 0, valueLength);
+ // });
+ // });
- // Apply calculated widths to each column, skipping column A
- worksheet['!cols'] = colWidths.map((width, index) => {
- if (index === 0) {
- return { wch: 8 }; // Set default or specific width for column A if needed
- }
- return { wch: width + 2 }; // Add padding to width
- });
+ // // Apply calculated widths to each column, skipping column A
+ // worksheet['!cols'] = colWidths.map((width, index) => {
+ // if (index === 0) {
+ // return { wch: 8 }; // Set default or specific width for column A if needed
+ // }
+ // return { wch: width + 2 }; // Add padding to width
+ // });
- // Format filename with date
- const today = new Date().toISOString().split('T')[0].replace(/-/g, '_'); // Get current date and format as YYYY_MM_DD
- const filename = `AR01_Late_Start_Report_${today}.xlsx`; // Append formatted date to the filename
+ // // Format filename with date
+ // const today = new Date().toISOString().split('T')[0].replace(/-/g, '_'); // Get current date and format as YYYY_MM_DD
+ // const filename = `AR01_Late_Start_Report_${today}.xlsx`; // Append formatted date to the filename
- // Convert workbook back to XLSX file
- XLSX.writeFile(workbook, filename);
- } else {
- throw new Error('Failed to load file');
- }
- };
- reader.readAsArrayBuffer(data);
- } catch (error) {
- console.error('Error downloading the file: ', error);
- }
+ // // Convert workbook back to XLSX file
+ // XLSX.writeFile(workbook, filename);
+ // } else {
+ // throw new Error('Failed to load file');
+ // }
+ // };
+ // reader.readAsArrayBuffer(data);
+ // } catch (error) {
+ // console.error('Error downloading the file: ', error);
+ // }
- //setIsLoading(false);
- };
+ // //setIsLoading(false);
+ // };
return (
diff --git a/src/components/SkillSearch/SkillSearch.tsx b/src/components/SkillSearch/SkillSearch.tsx
index c13ea19..9642827 100644
--- a/src/components/SkillSearch/SkillSearch.tsx
+++ b/src/components/SkillSearch/SkillSearch.tsx
@@ -8,6 +8,7 @@ import DeleteIcon from "@mui/icons-material/Delete";
import { useRouter } from "next/navigation";
import { deleteDialog, successDialog } from "../Swal/CustomAlerts";
import { SkillResult } from "@/app/api/skill";
+import { deleteSkill } from "@/app/api/skill/actions";
interface Props {
skill: SkillResult[];
@@ -29,7 +30,7 @@ const SkillSearch: React.FC = ({ skill }) => {
type: "text",
},
{
- label: t("Skill code"),
+ label: t("Skill Code"),
paramName: "code",
type: "text",
},
@@ -47,11 +48,11 @@ const SkillSearch: React.FC = ({ skill }) => {
);
const deleteClick = useCallback((skill: SkillResult) => {
- // deleteDialog(async () => {
- // await deleteStaff(skill.id);
- // successDialog("Delete Success", t);
- // setFilteredSkill((prev) => prev.filter((obj) => obj.id !== skill.id));
- // }, t);
+ deleteDialog(async () => {
+ await deleteSkill(skill.id);
+ successDialog("Delete Success", t);
+ // setFilteredSkill((prev) => prev.filter((obj) => obj.id !== skill.id));
+ }, t);
}, []);
const columns = useMemo[]>(
@@ -62,12 +63,12 @@ const SkillSearch: React.FC = ({ skill }) => {
onClick: onSkillClick,
buttonIcon: ,
},
- { name: "name", label: t("Name") },
- { name: "code", label: t("Code") },
- { name: "description", label: t("Description") },
+ { name: "name", label: t("name") },
+ { name: "code", label: t("code") },
+ { name: "description", label: t("description") },
{
- name: "action",
- label: t("Actions"),
+ name: "delete",
+ label: t("Delete"),
onClick: deleteClick,
buttonIcon: ,
color: "error",
diff --git a/src/components/SkillSearch/SkillSearchWrapper.tsx b/src/components/SkillSearch/SkillSearchWrapper.tsx
index 33d0547..0f721d1 100644
--- a/src/components/SkillSearch/SkillSearchWrapper.tsx
+++ b/src/components/SkillSearch/SkillSearchWrapper.tsx
@@ -17,7 +17,6 @@ interface SubComponents {
const SkillSearchWrapper: React.FC & SubComponents = async () => {
const skill = await fetchSkill()
- console.log(skill);
return ;
};
diff --git a/src/components/StaffSearch/StaffSearch.tsx b/src/components/StaffSearch/StaffSearch.tsx
index e19e915..7d2db97 100644
--- a/src/components/StaffSearch/StaffSearch.tsx
+++ b/src/components/StaffSearch/StaffSearch.tsx
@@ -61,7 +61,7 @@ const StaffSearch: React.FC = ({ staff, abilities }) => {
const onStaffClick = useCallback(
(staff: StaffResult) => {
- console.log(staff);
+ // console.log(staff);
const id = staff.id;
router.push(`/settings/staff/edit?id=${id}`);
},
@@ -70,8 +70,8 @@ const StaffSearch: React.FC = ({ staff, abilities }) => {
const onUserClick = useCallback(
(staff: StaffResult) => {
- console.log(staff);
- router.push(`/settings/staff/user?id=${staff.id}`);
+ // console.log(staff);
+ router.push(`/settings/staff/user?id=${staff.userId}`);
},
[router, t]
);
@@ -94,7 +94,7 @@ const StaffSearch: React.FC = ({ staff, abilities }) => {
},
{
name: "id",
- label: t("Actions"),
+ label: t("Users"),
onClick: onUserClick,
buttonIcon: ,
isHidden: ![MAINTAIN_USER, VIEW_USER].some((ability) => abilities.includes(ability)),
diff --git a/src/components/StyledDataGrid/StyledDataGrid.tsx b/src/components/StyledDataGrid/StyledDataGrid.tsx
index 743d288..3ea0dce 100644
--- a/src/components/StyledDataGrid/StyledDataGrid.tsx
+++ b/src/components/StyledDataGrid/StyledDataGrid.tsx
@@ -28,6 +28,10 @@ const StyledDataGrid = styled(DataGrid)(({ theme }) => ({
borderRadius: 0,
maxHeight: 50,
},
+ "& .MuiAutocomplete-root .MuiFilledInput-root": {
+ borderRadius: 0,
+ maxHeight: 50,
+ },
}));
export default StyledDataGrid;
diff --git a/src/components/TeamSearch/TeamSearch.tsx b/src/components/TeamSearch/TeamSearch.tsx
index 71ecb79..a73acdb 100644
--- a/src/components/TeamSearch/TeamSearch.tsx
+++ b/src/components/TeamSearch/TeamSearch.tsx
@@ -21,21 +21,28 @@ const TeamSearch: React.FC = ({ team }) => {
const { t } = useTranslation();
const [filteredTeam, setFilteredTeam] = useState(team);
const router = useRouter();
+ // translation
+ const edit = t("edit")
+ const name = t("name")
+ const code = t("code")
+ const description = t("description")
+ const teamLead = t("teamLead")
+ const delete_t = t("delete")
const searchCriteria: Criterion[] = useMemo(
() => [
{
- label: t("Team Name"),
+ label: name,
paramName: "name",
type: "text",
},
{
- label: t("Team Code"),
+ label: code,
paramName: "code",
type: "text",
},
{
- label: t("Team Description"),
+ label: description,
paramName: "description",
type: "text",
},
@@ -55,10 +62,7 @@ const TeamSearch: React.FC = ({ team }) => {
const onDeleteClick = useCallback((team: TeamResult) => {
deleteDialog(async () => {
await deleteTeam(team.id);
-
successDialog(t("Delete Success"), t);
-
- setFilteredTeam((prev) => prev.filter((obj) => obj.id !== team.id));
}, t);
}, []);
@@ -66,17 +70,17 @@ const TeamSearch: React.FC = ({ team }) => {
() => [
{
name: "action",
- label: t("Edit"),
+ label: edit,
onClick: onTeamClick,
buttonIcon: ,
},
- { name: "name", label: t("Name") },
- { name: "code", label: t("Code") },
- { name: "description", label: t("description") },
- { name: "staffName", label: t("TeamLead") },
+ { name: "name", label: name },
+ { name: "code", label: code },
+ { name: "description", label: description },
+ { name: "staffName", label: teamLead },
{
name: "action",
- label: t("Delete"),
+ label: delete_t,
onClick: onDeleteClick,
buttonIcon: ,
color: "error"
diff --git a/src/components/TimesheetTable/MobileTimesheetEntry.tsx b/src/components/TimesheetTable/MobileTimesheetEntry.tsx
index 9f709d8..03a0487 100644
--- a/src/components/TimesheetTable/MobileTimesheetEntry.tsx
+++ b/src/components/TimesheetTable/MobileTimesheetEntry.tsx
@@ -17,6 +17,7 @@ import { AssignedProject, ProjectWithTasks } from "@/app/api/projects";
import TimesheetEditModal, {
Props as TimesheetEditModalProps,
} from "./TimesheetEditModal";
+import TimeEntryCard from "./TimeEntryCard";
interface Props {
date: string;
@@ -119,91 +120,13 @@ const MobileTimesheetEntry: React.FC = ({
const task = project?.tasks.find((t) => t.id === entry.taskId);
return (
-
-
-
-
-
- {project
- ? `${project.code} - ${project.name}`
- : t("Non-billable Task")}
-
- {task && (
-
- {task.name}
-
- )}
-
-
-
-
-
-
-
-
- {t("Hours")}
-
-
- {manhourFormatter.format(entry.inputHours || 0)}
-
-
-
-
- {t("Other Hours")}
-
-
- {manhourFormatter.format(entry.otHours || 0)}
-
-
-
- {entry.remark && (
-
-
- {t("Remark")}
-
- {entry.remark}
-
- )}
-
-
+ project={project}
+ task={task}
+ entry={entry}
+ onEdit={openEditModal(entry)}
+ />
);
})
) : (
diff --git a/src/components/TimesheetTable/ProjectSelect.tsx b/src/components/TimesheetTable/ProjectSelect.tsx
index 762512c..3c1ab51 100644
--- a/src/components/TimesheetTable/ProjectSelect.tsx
+++ b/src/components/TimesheetTable/ProjectSelect.tsx
@@ -10,6 +10,7 @@ import {
import { AssignedProject, ProjectWithTasks } from "@/app/api/projects";
import { useTranslation } from "react-i18next";
import differenceBy from "lodash/differenceBy";
+import { TFunction } from "i18next";
interface Props {
allProjects: ProjectWithTasks[];
@@ -18,119 +19,159 @@ interface Props {
onProjectSelect: (projectId: number | string) => void;
}
-// const AutocompleteProjectSelect: React.FC = ({
-// allProjects,
-// assignedProjects,
-// value,
-// onProjectSelect,
-// }) => {
-// const { t } = useTranslation("home");
-// const nonAssignedProjects = useMemo(() => {
-// return differenceBy(allProjects, assignedProjects, "id");
-// }, [allProjects, assignedProjects]);
-
-// const options = useMemo(() => {
-// return [
-// {
-// value: "",
-// label: t("None"),
-// group: "non-billable",
-// },
-// ...assignedProjects.map((p) => ({
-// value: p.id,
-// label: `${p.code} - ${p.name}`,
-// group: "assigned",
-// })),
-// ...nonAssignedProjects.map((p) => ({
-// value: p.id,
-// label: `${p.code} - ${p.name}`,
-// group: "non-assigned",
-// })),
-// ];
-// }, [assignedProjects, nonAssignedProjects, t]);
-
-// return (
-// option.group}
-// getOptionLabel={(option) => option.label}
-// options={options}
-// renderInput={(params) => }
-// />
-// );
-// };
+const getGroupName = (t: TFunction, groupName: string): string => {
+ switch (groupName) {
+ case "non-billable":
+ return t("Non-billable");
+ case "assigned":
+ return t("Assigned Projects");
+ case "non-assigned":
+ return t("Non-assigned Projects");
+ default:
+ return t("Ungrouped");
+ }
+};
-const ProjectSelect: React.FC = ({
+const AutocompleteProjectSelect: React.FC = ({
allProjects,
assignedProjects,
value,
onProjectSelect,
}) => {
const { t } = useTranslation("home");
-
const nonAssignedProjects = useMemo(() => {
return differenceBy(allProjects, assignedProjects, "id");
}, [allProjects, assignedProjects]);
+ const options = useMemo(() => {
+ return [
+ {
+ value: "",
+ label: t("None"),
+ group: "non-billable",
+ },
+ ...assignedProjects.map((p) => ({
+ value: p.id,
+ label: `${p.code} - ${p.name}`,
+ group: "assigned",
+ })),
+ ...nonAssignedProjects.map((p) => ({
+ value: p.id,
+ label: `${p.code} - ${p.name}`,
+ group: "non-assigned",
+ })),
+ ];
+ }, [assignedProjects, nonAssignedProjects, t]);
+
+ const currentValue = options.find((o) => o.value === value) || options[0];
+
const onChange = useCallback(
- (event: SelectChangeEvent) => {
- const newValue = event.target.value;
- onProjectSelect(newValue);
+ (event: React.SyntheticEvent, newValue: { value: number | string }) => {
+ onProjectSelect(newValue.value);
},
[onProjectSelect],
);
return (
- option.group}
+ getOptionLabel={(option) => option.label}
+ options={options}
+ renderGroup={(params) => (
+ <>
+
+ {getGroupName(t, params.group)}
+
+ {params.children}
+ >
+ )}
+ renderOption={(params, option) => {
+ return (
+
+ );
}}
- >
- {t("Non-billable")}
-
- {assignedProjects.length > 0 && [
-
- {t("Assigned Projects")}
- ,
- ...assignedProjects.map((project) => (
-
- )),
- ]}
- {nonAssignedProjects.length > 0 && [
-
- {t("Non-assigned Projects")}
- ,
- ...nonAssignedProjects.map((project) => (
-
- )),
- ]}
-
+ renderInput={(params) => }
+ />
);
};
-export default ProjectSelect;
+// const ProjectSelect: React.FC = ({
+// allProjects,
+// assignedProjects,
+// value,
+// onProjectSelect,
+// }) => {
+// const { t } = useTranslation("home");
+
+// const nonAssignedProjects = useMemo(() => {
+// return differenceBy(allProjects, assignedProjects, "id");
+// }, [allProjects, assignedProjects]);
+
+// const onChange = useCallback(
+// (event: SelectChangeEvent) => {
+// const newValue = event.target.value;
+// onProjectSelect(newValue);
+// },
+// [onProjectSelect],
+// );
+
+// return (
+//
+// {t("Non-billable")}
+//
+// {assignedProjects.length > 0 && [
+//
+// {t("Assigned Projects")}
+// ,
+// ...assignedProjects.map((project) => (
+//
+// )),
+// ]}
+// {nonAssignedProjects.length > 0 && [
+//
+// {t("Non-assigned Projects")}
+// ,
+// ...nonAssignedProjects.map((project) => (
+//
+// )),
+// ]}
+//
+// );
+// };
+
+export default AutocompleteProjectSelect;
diff --git a/src/components/TimesheetTable/TimeEntryCard.tsx b/src/components/TimesheetTable/TimeEntryCard.tsx
new file mode 100644
index 0000000..445e182
--- /dev/null
+++ b/src/components/TimesheetTable/TimeEntryCard.tsx
@@ -0,0 +1,87 @@
+import { ProjectWithTasks } from "@/app/api/projects";
+import { Task } from "@/app/api/tasks";
+import { TimeEntry } from "@/app/api/timesheets/actions";
+import { manhourFormatter } from "@/app/utils/formatUtil";
+import { Edit } from "@mui/icons-material";
+import { Box, Card, CardContent, IconButton, Typography } from "@mui/material";
+import React from "react";
+import { useTranslation } from "react-i18next";
+
+interface Props {
+ project?: ProjectWithTasks;
+ task?: Task;
+ entry: TimeEntry;
+ onEdit?: () => void;
+}
+
+const TimeEntryCard: React.FC = ({ project, task, entry, onEdit }) => {
+ const { t } = useTranslation("home");
+ return (
+
+
+
+
+
+ {project
+ ? `${project.code} - ${project.name}`
+ : t("Non-billable Task")}
+
+ {task && (
+
+ {task.name}
+
+ )}
+
+ {onEdit && (
+
+
+
+ )}
+
+
+
+
+ {t("Hours")}
+
+
+ {manhourFormatter.format(entry.inputHours || 0)}
+
+
+
+
+ {t("Other Hours")}
+
+
+ {manhourFormatter.format(entry.otHours || 0)}
+
+
+
+ {entry.remark && (
+
+
+ {t("Remark")}
+
+ {entry.remark}
+
+ )}
+
+
+ );
+};
+
+export default TimeEntryCard;
diff --git a/src/components/UserGroupSearch/UserGroupSearch.tsx b/src/components/UserGroupSearch/UserGroupSearch.tsx
index fee25e4..7a8f7dd 100644
--- a/src/components/UserGroupSearch/UserGroupSearch.tsx
+++ b/src/components/UserGroupSearch/UserGroupSearch.tsx
@@ -45,10 +45,7 @@ const UserGroupSearch: React.FC = ({ users }) => {
const onDeleteClick = useCallback((group: UserGroupResult) => {
deleteDialog(async () => {
await deleteGroup(group.id);
-
successDialog(t("Delete Success"), t);
-
- setFilteredUser((prev) => prev.filter((obj) => obj.id !== group.id));
}, t);
}, []);
diff --git a/src/components/UserSearch/UserSearch.tsx b/src/components/UserSearch/UserSearch.tsx
index 658d25c..b7ac669 100644
--- a/src/components/UserSearch/UserSearch.tsx
+++ b/src/components/UserSearch/UserSearch.tsx
@@ -44,10 +44,7 @@ const UserSearch: React.FC = ({ users }) => {
const onDeleteClick = useCallback((users: UserResult) => {
deleteDialog(async () => {
await deleteUser(users.id);
-
successDialog(t("Delete Success"), t);
-
- setFilteredUser((prev) => prev.filter((obj) => obj.id !== users.id));
}, t);
}, []);
diff --git a/src/components/UserWorkspacePage/ProjectGrid.tsx b/src/components/UserWorkspacePage/ProjectGrid.tsx
index ad44116..275d48d 100644
--- a/src/components/UserWorkspacePage/ProjectGrid.tsx
+++ b/src/components/UserWorkspacePage/ProjectGrid.tsx
@@ -56,9 +56,6 @@ const ProjectGrid: React.FC = ({ projects }) => {
)})`}
{/* Hours Allocated */}
-
- {t("Hours Allocated:")}
-
= ({ projects }) => {
alignItems: "baseline",
}}
>
- {t("Normal")}
+
+ {t("Hours Allocated:")}
+
{manhourFormatter.format(project.hoursAllocated)}
-
- {t("(Others)")}
- {`(${manhourFormatter.format(
- project.hoursAllocatedOther,
- )})`}
-
diff --git a/src/components/UserWorkspacePage/UserWorkspacePage.tsx b/src/components/UserWorkspacePage/UserWorkspacePage.tsx
index 062e233..0c8615f 100644
--- a/src/components/UserWorkspacePage/UserWorkspacePage.tsx
+++ b/src/components/UserWorkspacePage/UserWorkspacePage.tsx
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { Add } from "@mui/icons-material";
-import { Typography } from "@mui/material";
+import { Box, Typography } from "@mui/material";
import ButtonGroup from "@mui/material/ButtonGroup";
import AssignedProjects from "./AssignedProjects";
import TimesheetModal from "../TimesheetModal";
@@ -16,6 +16,8 @@ import {
} from "@/app/api/timesheets/actions";
import LeaveModal from "../LeaveModal";
import { LeaveType } from "@/app/api/timesheets";
+import { CalendarIcon } from "@mui/x-date-pickers";
+import PastEntryCalendarModal from "../PastEntryCalendar/PastEntryCalendarModal";
export interface Props {
leaveTypes: LeaveType[];
@@ -36,6 +38,7 @@ const UserWorkspacePage: React.FC = ({
}) => {
const [isTimeheetModalVisible, setTimeheetModalVisible] = useState(false);
const [isLeaveModalVisible, setLeaveModalVisible] = useState(false);
+ const [isPastEventModalVisible, setPastEventModalVisible] = useState(false);
const { t } = useTranslation("home");
const handleAddTimesheetButtonClick = useCallback(() => {
@@ -54,6 +57,14 @@ const UserWorkspacePage: React.FC = ({
setLeaveModalVisible(false);
}, []);
+ const handlePastEventClick = useCallback(() => {
+ setPastEventModalVisible(true);
+ }, []);
+
+ const handlePastEventClose = useCallback(() => {
+ setPastEventModalVisible(false);
+ }, []);
+
return (
<>
= ({
{t("User Workspace")}
-
+
+ }
+ variant="outlined"
+ onClick={handlePastEventClick}
+ >
+ {t("View Past Entries")}
+
} onClick={handleAddTimesheetButtonClick}>
{t("Enter Time")}
@@ -79,8 +92,14 @@ const UserWorkspacePage: React.FC = ({
{t("Record Leave")}
-
+
+