From 4576b10da29c2785bd020a39684305af54c0758b Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Tue, 14 May 2024 17:13:41 +0800 Subject: [PATCH 1/6] Add lang button --- src/components/AppBar/Profile.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) 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} - {router.replace("/settings/changepassword")}}>{t("Change Password")} + { router.replace("/settings/changepassword") }}>{t("Change Password")} + {language === "zh" && { onLangClick("en") }}>{t("Change To English Version")}} + {language === "en" && { onLangClick("zh") }}>{t("Change To Chinese Version")}} signOut()}>{t("Sign out")} From f2f734e9d9b96b6827e92be00a4cdbcdadf9131a Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Tue, 14 May 2024 18:08:39 +0800 Subject: [PATCH 2/6] update --- src/app/(main)/settings/skill/create/page.tsx | 6 +- src/app/(main)/settings/skill/edit/page.tsx | 4 +- src/app/(main)/settings/skill/page.tsx | 2 +- src/app/(main)/settings/team/create/page.tsx | 28 +---- src/app/(main)/settings/team/page.tsx | 4 +- src/app/api/group/actions.ts | 16 ++- src/app/api/skill/actions.ts | 25 ++++- src/app/api/skill/index.ts | 7 +- src/app/api/staff/actions.ts | 33 ++++-- src/app/api/staff/index.ts | 1 + src/app/api/team/actions.ts | 17 ++- src/app/api/user/actions.ts | 22 ++-- src/app/utils/fetchUtil.ts | 11 +- .../ChangePassword/ChangePassword.tsx | 1 - src/components/CreateSkill/CreateSkill.tsx | 59 +++++----- src/components/CreateSkill/SkillInfo.tsx | 104 ++++++++++-------- src/components/CreateStaff/CreateStaff.tsx | 30 +++-- src/components/CreateStaff/StaffInfo.tsx | 37 ++++--- src/components/CreateTeam/CreateTeam.tsx | 9 +- src/components/CreateTeam/StaffAllocation.tsx | 5 +- src/components/EditSkill/EditSkill.tsx | 79 ++++++------- src/components/EditSkill/EditSkillForm.tsx | 68 +++++------- src/components/EditTeam/Allocation.tsx | 15 +-- src/components/EditTeam/EditTeam.tsx | 14 +-- src/components/SkillSearch/SkillSearch.tsx | 23 ++-- .../SkillSearch/SkillSearchWrapper.tsx | 1 - src/components/StaffSearch/StaffSearch.tsx | 8 +- src/components/TeamSearch/TeamSearch.tsx | 28 +++-- .../UserGroupSearch/UserGroupSearch.tsx | 3 - src/components/UserSearch/UserSearch.tsx | 3 - src/i18n/zh/skill.json | 18 +++ src/i18n/zh/staff.json | 1 + src/i18n/zh/team.json | 25 +++++ 33 files changed, 385 insertions(+), 322 deletions(-) create mode 100644 src/i18n/zh/skill.json create mode 100644 src/i18n/zh/team.json 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/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/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/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/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/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 && } */} + + 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/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/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/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/i18n/zh/skill.json b/src/i18n/zh/skill.json new file mode 100644 index 0000000..ad6e848 --- /dev/null +++ b/src/i18n/zh/skill.json @@ -0,0 +1,18 @@ +{ + "Actions": "編輯", + "Skill": "技能", + "Skill Name": "名稱", + "Skill Code": "編號", + "Skill Info": "技能詳情", + "name": "技能", + "code": "編號", + "description": "描述", + "Confirm": "確定", + "Cancel": "取消", + "Delete": "刪除", + "Reset": "重設", + "Edit Skill": "編輯技能", + "Create Skill": "建立技能", + "Please input correct ": "請輸入正確" + +} \ No newline at end of file diff --git a/src/i18n/zh/staff.json b/src/i18n/zh/staff.json index 78bd69e..3c1ad59 100644 --- a/src/i18n/zh/staff.json +++ b/src/i18n/zh/staff.json @@ -6,6 +6,7 @@ "Grade": "級別", "Current Position": "現職", "Actions": "編輯", + "Users": "用戶", "Create Staff": "新增員工", "Company": "公司", "Department": "部門", diff --git a/src/i18n/zh/team.json b/src/i18n/zh/team.json new file mode 100644 index 0000000..0735308 --- /dev/null +++ b/src/i18n/zh/team.json @@ -0,0 +1,25 @@ +{ + "Team": "隊伍", + "Team Info": "隊伍資料", + "Staff Allocation": "員工分配", + "Create Team": "建立隊伍", + "Edit Team": "編輯隊伍", + "name": "名稱", + "code": "編號", + "description": "描述", + "Team Description": "隊伍描述", + "edit": "編輯", + "teamLead": "負責人", + "Confirm": "確定", + "Cancel": "取消", + "delete": "刪除", + "add": "加入", + "remove": "移除", + "Staff Pool": "可分配員工", + "Allocated Staff": "已分配員工", + "Staff Id": "員工編號", + "Staff Name": "員工名稱", + "Position": "職位", + "Search by Staff Id, Name or Position.": "按員工編號、姓名或職位搜索", + "An error has occurred. Please try again later.": "發生了錯誤。請稍後再試" +} \ No newline at end of file From 8479f6fd108d5e89a22008be67edbd671c04a232 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Tue, 14 May 2024 18:31:01 +0800 Subject: [PATCH 3/6] update project --- src/app/(main)/layout.tsx | 11 +- src/app/api/customer/index.ts | 3 + src/app/api/projects/actions.ts | 7 +- src/app/api/projects/index.ts | 1 + src/app/api/subsidiary/index.ts | 8 +- .../CreateProject/CreateProject.tsx | 25 ++- .../CreateProject/ProjectClientDetails.tsx | 183 +++++++++++------- src/components/CreateProject/TaskSetup.tsx | 42 +++- .../ProjectSearch/ProjectSearch.tsx | 10 +- 9 files changed, 198 insertions(+), 92 deletions(-) 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/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/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..697ac98 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 { 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/components/CreateProject/CreateProject.tsx b/src/components/CreateProject/CreateProject.tsx index c4aa9d5..a54931b 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 ( @@ -114,6 +114,8 @@ const CreateProject: React.FC = ({ const { t } = useTranslation(); const router = useRouter(); +console.log(defaultInputs) + const handleCancel = () => { router.replace("/projects"); }; @@ -219,6 +221,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 +251,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 +270,7 @@ const CreateProject: React.FC = ({ allocatedStaffIds: [], milestones: {}, totalManhour: 0, + taskTemplateId: "All", ...defaultInputs, // manhourPercentageByGrade should have a sensible default @@ -289,7 +294,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" ) && ( + + )} + + ); +}; + +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/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/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 ( - + 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 ( +// +// ); +// }; + +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/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")} - + + - + +