Browse Source

Merge branch 'main' of https://git.2fi-solutions.com/wayne.lee/tsms

tags/Baseline_30082024_FRONTEND_UAT
leoho2fi 1 year ago
parent
commit
b8ddb8e5d7
27 changed files with 560 additions and 81 deletions
  1. +0
    -0
      src/app/(main)/changepassword/page.tsx
  2. +21
    -0
      src/app/(main)/dashboard/bspp/page.tsx
  3. +5
    -2
      src/app/(main)/settings/group/edit/page.tsx
  4. +13
    -0
      src/app/api/group/index.ts
  5. +1
    -1
      src/app/api/reports/index.ts
  6. +5
    -5
      src/app/api/staff/actions.ts
  7. +1
    -0
      src/app/api/user/actions.ts
  8. +1
    -1
      src/components/AppBar/Profile.tsx
  9. +404
    -0
      src/components/Bspp/Bspp.tsx
  10. +10
    -0
      src/components/Bspp/BsppWrapper.tsx
  11. +1
    -0
      src/components/Bspp/index.ts
  12. +6
    -3
      src/components/CreateProject/CreateProject.tsx
  13. +1
    -1
      src/components/CreateProject/StaffAllocation.tsx
  14. +2
    -10
      src/components/CreateStaff/CreateStaff.tsx
  15. +13
    -9
      src/components/CreateStaff/StaffInfo.tsx
  16. +2
    -2
      src/components/EditStaff/EditStaff.tsx
  17. +2
    -2
      src/components/EditStaff/StaffInfo.tsx
  18. +1
    -1
      src/components/EditStaffForm/EditStaffForm.tsx
  19. +1
    -1
      src/components/EditTeam/EditTeam.tsx
  20. +4
    -2
      src/components/EditUser/EditUser.tsx
  21. +8
    -6
      src/components/EditUser/EditUserWrapper.tsx
  22. +2
    -2
      src/components/EditUser/UserDetail.tsx
  23. +28
    -24
      src/components/EditUserGroup/EditUserGroup.tsx
  24. +12
    -5
      src/components/EditUserGroup/EditUserGroupWrapper.tsx
  25. +1
    -0
      src/components/EditUserGroup/GroupInfo.tsx
  26. +9
    -3
      src/components/GenerateMonthlyWorkHoursReport/GenerateMonthlyWorkHoursReport.tsx
  27. +6
    -1
      src/components/NavigationContent/NavigationContent.tsx

src/app/(main)/settings/changepassword/page.tsx → src/app/(main)/changepassword/page.tsx View File


+ 21
- 0
src/app/(main)/dashboard/bspp/page.tsx View File

@@ -0,0 +1,21 @@
import { Metadata } from "next";
import { I18nProvider } from "@/i18n";
import { Suspense } from "react";
import Typography from "@mui/material/Typography";
import Bspp from "@/components/Bspp";

export const metadata: Metadata = {
title: "Project Status by Client",
};

const CompanyTeamCashFlow: React.FC = () => {
return (
<I18nProvider namespaces={["dashboard"]}>
<Typography variant="h4" marginInlineEnd={2}>
BSPP
</Typography>
<Bspp />
</I18nProvider>
);
};
export default CompanyTeamCashFlow;

+ 5
- 2
src/app/(main)/settings/group/edit/page.tsx View File

@@ -1,3 +1,4 @@
import { searchParamsProps } from "@/app/utils/fetchUtil";
import EditPosition from "@/components/EditPosition";
import EditUserGroup from "@/components/EditUserGroup";
import { I18nProvider, getServerI18n } from "@/i18n";
@@ -8,7 +9,9 @@ export const metadata: Metadata = {
title: "Edit User Group",
};

const Group: React.FC = async () => {
const Group: React.FC<searchParamsProps> = async ({
searchParams,
}) => {
const { t } = await getServerI18n("group");

// Preload necessary dependencies
@@ -17,7 +20,7 @@ const Group: React.FC = async () => {
<>
{/* <Typography variant="h4">{t("Edit User Group")}</Typography> */}
<I18nProvider namespaces={["group"]}>
<EditUserGroup />
<EditUserGroup id={parseInt(searchParams.id as string)}/>
</I18nProvider>
</>
);


+ 13
- 0
src/app/api/group/index.ts View File

@@ -14,8 +14,21 @@ export interface UserGroupResult {
description: string;
}

export type IndivUserGroup = {
authIds: number[];
data: any;
userIds: number[];
}

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


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

+ 1
- 1
src/app/api/reports/index.ts View File

@@ -46,7 +46,7 @@ export interface ProjectPotentialDelayReportRequest {
// - Monthly Work Hours Report
export interface MonthlyWorkHoursReportFilter {
staff: string[];
date: any;
date: string;
}

export interface MonthlyWorkHoursReportRequest {


+ 5
- 5
src/app/api/staff/actions.ts View File

@@ -20,17 +20,17 @@ export interface CreateStaffInputs {
companyId: number;
salaryId: number;
skillSetId?: number[];
joinDate: string;
joinDate?: string;
currentPositionId: number;
joinPositionId: number;
joinPositionId?: number;
gradeId?: number;
teamId?: number
departmentId: number;
departmentId?: number;
phone1: string;
phone2?: string;
email: string;
emergContactName: string;
emergContactPhone: string;
emergContactName?: string;
emergContactPhone?: string;
employType: string;
departDate?: string;
departReason?: string;


+ 1
- 0
src/app/api/user/actions.ts View File

@@ -7,6 +7,7 @@ import { UserDetail, UserResult } from ".";
import { cache } from "react";

export interface UserInputs {
username: string;
name: string;
email?: string;
addAuthIds?: number[];


+ 1
- 1
src/components/AppBar/Profile.tsx View File

@@ -63,7 +63,7 @@ const Profile: React.FC<Props> = ({ avatarImageSrc, profileName }) => {
{profileName}
</Typography>
<Divider />
<MenuItem onClick={() => { router.replace("/settings/changepassword") }}>{t("Change Password")}</MenuItem>
<MenuItem onClick={() => { router.replace("/changepassword") }}>{t("Change Password")}</MenuItem>
{language === "zh" && <MenuItem onClick={() => { onLangClick("en") }}>{t("Change To English Version")}</MenuItem>}
{language === "en" && <MenuItem onClick={() => { onLangClick("zh") }}>{t("Change To Chinese Version")}</MenuItem>}
<MenuItem onClick={() => signOut()}>{t("Sign out")}</MenuItem>


+ 404
- 0
src/components/Bspp/Bspp.tsx View File

@@ -0,0 +1,404 @@
"use client";
import * as React from "react";
import Grid from "@mui/material/Grid";
import { Card, CardHeader } from "@mui/material";
import ReactApexChart from "react-apexcharts";
import { ApexOptions } from "apexcharts";
import "../../app/global.css";
import { Input, Label } from "reactstrap";
import Select, { components } from "react-select";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import dayjs, { Dayjs } from "dayjs";
import isBetweenPlugin from "dayjs/plugin/isBetween";
import { PickersDay, PickersDayProps } from "@mui/x-date-pickers/PickersDay";
import { styled } from "@mui/material/styles";

dayjs.extend(isBetweenPlugin);
interface CustomPickerDayProps extends PickersDayProps<Dayjs> {
isSelected: boolean;
isHovered: boolean;
}

// Style Day
const CustomPickersDay = styled(PickersDay, {
shouldForwardProp: (prop) => prop !== "isSelected" && prop !== "isHovered",
})<CustomPickerDayProps>(({ theme, isSelected, isHovered, day }) => ({
borderRadius: 0,
...(isSelected && {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
"&:hover, &:focus": {
backgroundColor: theme.palette.primary.main,
},
}),
...(isHovered && {
backgroundColor: theme.palette.primary[theme.palette.mode],
"&:hover, &:focus": {
backgroundColor: theme.palette.primary[theme.palette.mode],
},
}),
...(day.day() === 0 && {
borderTopLeftRadius: "50%",
borderBottomLeftRadius: "50%",
}),
...(day.day() === 6 && {
borderTopRightRadius: "50%",
borderBottomRightRadius: "50%",
}),
})) as React.ComponentType<CustomPickerDayProps>;

const isInSameWeek = (dayA: Dayjs, dayB: Dayjs | null | undefined) => {
if (dayB == null) {
return false;
}

return dayA.isSame(dayB, "week");
};

function Day(
props: PickersDayProps<Dayjs> & {
selectedDay?: Dayjs | null;
hoveredDay?: Dayjs | null;
},
) {
const { day, selectedDay, hoveredDay, ...other } = props;

return (
<CustomPickersDay
{...other}
day={day}
sx={{ px: 2.5 }}
disableMargin
selected={false}
isSelected={isInSameWeek(day, selectedDay)}
isHovered={isInSameWeek(day, hoveredDay)}
/>
);
}



// Main function
const Bspp: React.FC = () => {

// Set Date
const todayDate = new Date();
const firstDayOfWeek = new Date();
const lastDayOfWeek = new Date();
firstDayOfWeek.setDate(todayDate.getDate() - todayDate.getDay() + 1);
lastDayOfWeek.setDate(todayDate.getDate() - todayDate.getDay() + 7);
const [teamTotalManhoursSpentSelect, setTeamTotalManhoursSpentSelect]: any =
React.useState("Weekly");
const weekDates: any[] = [];
const monthDates: any[] = [];
const currentDate = dayjs();
const sixMonthsAgo = currentDate.subtract(6, "month");
for (let i = 0; i < 7; i++) {
const currentDate = new Date(firstDayOfWeek);
currentDate.setDate(firstDayOfWeek.getDate() + i);
const formattedDate = dayjs(currentDate).format("DD MMM (ddd)");
weekDates.push(formattedDate);
}
for (
let date = sixMonthsAgo.clone();
date.isBefore(currentDate, "month");
date = date.add(1, "month")
) {
monthDates.push(date.format("MM-YYYY"));
}
monthDates.push(currentDate.format("MM-YYYY"));
const [teamTotalManhoursSpentPeriod, setTeamTotalManhoursSpentPeriod]: any[] =
React.useState(weekDates);

// Set fake Data
const [
teamTotalManhoursSpentPlanData,
setTeamTotalManhoursSpentPlanData,
]: any[] = React.useState([42, 42, 42, 42, 42, 0, 0]);
const [
teamTotalManhoursSpentActualData,
setTeamTotalManhoursSpentActualData,
]: any[] = React.useState([45, 42, 60, 42, 58, 0, 0]);
const [hoveredDay, setHoveredDay] = React.useState<Dayjs | null>(null);
const [value, setValue] = React.useState<Dayjs | null>(dayjs());
const [totalManHoursMonthlyFromValue, setTotalManHoursMonthlyFromValue] =
React.useState<Dayjs>(dayjs(new Date()).subtract(6, "month"));
const [totalManHoursMonthlyToValue, setTotalManHoursMonthlyToValue] =
React.useState<Dayjs>(dayjs());
const [totalManHoursMaxValue, setTotalManHoursMaxValue] = React.useState(75);

const teamOptions = [
{ value: 1, label: "XXX Team" },
{ value: 2, label: "YYY Team" },
{ value: 3, label: "ZZZ Team" },
];

// https://poe.com/
// https://apexcharts.com/docs/chart-types/line-chart/
// chart options
const options: ApexOptions = {
chart: {
height: 350,
type: "line",
},
stroke: {
width: [2, 2],
},
plotOptions: {
bar: {
horizontal: false,
distributed: false,
},
},
dataLabels: {
enabled: true,
},
xaxis: {
categories: teamTotalManhoursSpentPeriod,
},
yaxis: [
{
title: {
text: "Team Total Manhours Spent (Hour)",
},
min: 0,
max: totalManHoursMaxValue,
tickAmount: 5,
},
],
grid: {
borderColor: "#f1f1f1",
},
annotations: {},
series: [
{
name: "Planned",
type: "line",
color: "#efbe7d",
data: teamTotalManhoursSpentPlanData,
},
{
name: "Actual",
type: "line",
color: "#7cd3f2",
data: teamTotalManhoursSpentActualData,
},
],
};

// On click function
const teamTotalManhoursSpentOnClick = (r: any) => {
setTeamTotalManhoursSpentSelect(r);
if (r === "Weekly") {
setValue(dayjs(new Date()));
setTeamTotalManhoursSpentPeriod(weekDates);
setTeamTotalManhoursSpentPlanData([42, 42, 42, 42, 42, 0, 0]);
setTeamTotalManhoursSpentActualData([45, 42, 60, 42, 58, 0, 0]);
setTotalManHoursMaxValue(75);
} else if (r === "Monthly") {
setTeamTotalManhoursSpentPeriod(monthDates);
setTeamTotalManhoursSpentPlanData([840, 840, 840, 840, 840, 840]);
setTeamTotalManhoursSpentActualData([900, 840, 1200, 840, 1160, 840]);
setTotalManHoursMaxValue(1250);
}
};

const selectWeeklyPeriod = (r: any) => {
const selectDate = new Date(r);
const firstDayOfWeek = new Date();
firstDayOfWeek.setDate(selectDate.getDate() - selectDate.getDay() + 0);
const weekDates: any[] = [];
for (let i = 0; i < 7; i++) {
const currentDate = new Date(firstDayOfWeek);
currentDate.setDate(firstDayOfWeek.getDate() + i);
const formattedDate = dayjs(currentDate).format("DD MMM (ddd)");
weekDates.push(formattedDate);
}
setTeamTotalManhoursSpentPeriod(weekDates);
setValue(dayjs(firstDayOfWeek));
};

const selectMonthlyPeriodFrom = (r: any) => {
const monthDates: any[] = [];
const monthPlanData: any[] = [];
const monthActualData: any[] = [];
const selectFromDate = dayjs(r);
for (
let date = selectFromDate.clone();
date.isBefore(totalManHoursMonthlyToValue, "month");
date = date.add(1, "month")
) {
monthDates.push(date.format("MM-YYYY"));
monthPlanData.push(840);
monthActualData.push(Math.floor(Math.random() * (1200 - 840) + 840));
}
monthDates.push(totalManHoursMonthlyToValue.format("MM-YYYY"));
monthPlanData.push(840);
monthActualData.push(Math.floor(Math.random() * (1200 - 840) + 840));
setTeamTotalManhoursSpentPlanData(monthPlanData);
setTeamTotalManhoursSpentActualData(monthActualData);
setTeamTotalManhoursSpentPeriod(monthDates);
};

const selectMonthlyPeriodTo = (r: any) => {
const monthDates: any[] = [];
const monthPlanData: any[] = [];
const monthActualData: any[] = [];
const selectToDate = dayjs(r);
for (
let date = totalManHoursMonthlyFromValue.clone();
date.isBefore(selectToDate, "month");
date = date.add(1, "month")
) {
monthDates.push(date.format("MM-YYYY"));
monthPlanData.push(840);
monthActualData.push(Math.floor(Math.random() * (1200 - 840) + 840));
}
monthDates.push(selectToDate.format("MM-YYYY"));
monthPlanData.push(840);
monthActualData.push(Math.floor(Math.random() * (1200 - 840) + 840));
setTeamTotalManhoursSpentPlanData(monthPlanData);
setTeamTotalManhoursSpentActualData(monthActualData);
setTeamTotalManhoursSpentPeriod(monthDates);
};

// render
return (
<>
<Grid item sm>
<div style={{ display: "inline-block", width: "40%" }}>
<div>
<Grid item xs={12} md={12} lg={12}>
<Card>
<CardHeader
className="text-slate-500"
title="Team Total Manhours Spent"
/>
<div style={{ display: "inline-block", width: "99%" }}>
{/* Weekly / Monthly Button select */}
{/* Different: selected style & on click function */}
<div className="w-fit align-top mr-5 float-right">
{teamTotalManhoursSpentSelect === "Weekly" && (
<>
<button className="text-lg bg-violet-100 border-violet-500 text-violet-500 border-solid rounded-l-md w-32">
Weekly
</button>
<button
onClick={() =>
teamTotalManhoursSpentOnClick("Monthly")
}
className="hover:cursor-pointer hover:bg-violet-50 text-lg bg-transparent border-violet-500 text-violet-500 border-solid rounded-r-md w-32"
>
Monthly
</button>
</>
)}
{teamTotalManhoursSpentSelect === "Monthly" && (
<>
<button
onClick={() =>
teamTotalManhoursSpentOnClick("Weekly")
}
className="hover:cursor-pointer hover:bg-violet-50 text-lg bg-transparent border-violet-500 text-violet-500 border-solid rounded-l-md w-32"
>
Weekly
</button>
<button className="text-lg bg-violet-100 border-violet-500 text-violet-500 border-solid rounded-r-md w-32">
Monthly
</button>
</>
)}
</div>

{/* Team combo */}
<div className="inline-block w-fit mt-2">

{/* Label */}
<div className="inline-block ml-6">
<Label className="text-slate-500 font-medium">
Team:&nbsp;
</Label>
</div>

{/* Select Option */}
<div className="inline-block ml-1 w-60">
<Select
placeholder="All Team"
options={teamOptions}
isClearable={true}
/>
</div>

{/* Weekly / Monthly Period */}
<div className="ml-6 mt-2" style={{ verticalAlign: "top" }}>
{teamTotalManhoursSpentSelect === "Weekly" && (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
className="w-72 h-10 align-top"
label="Period:"
value={value}
format="DD-MM-YYYY"
onChange={(newValue) =>
selectWeeklyPeriod(newValue)
}
showDaysOutsideCurrentMonth
displayWeekNumber
slots={{ day: Day }}
slotProps={{
day: (ownerState) =>
({
selectedDay: value,
hoveredDay,
onPointerEnter: () =>
setHoveredDay(ownerState.day),
onPointerLeave: () => setHoveredDay(null),
}) as any,
}}
/>
</LocalizationProvider>
)}
{teamTotalManhoursSpentSelect === "Monthly" && (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
className="w-40 h-10 align-top"
onChange={(newValue) =>
selectMonthlyPeriodFrom(newValue)
}
defaultValue={totalManHoursMonthlyFromValue}
label={"Form"}
views={["month", "year"]}
/>
<DatePicker
className="w-40 h-10 align-top"
onChange={(newValue) =>
selectMonthlyPeriodTo(newValue)
}
defaultValue={totalManHoursMonthlyToValue}
label={"To"}
views={["month", "year"]}
/>
</LocalizationProvider>
)}
</div>
</div>

{/* Render Apex Chart */}
<ReactApexChart
options={options}
series={options.series}
type="line"
height="400"
/>
</div>
</Card>
</Grid>
</div>
</div>
</Grid>
</>
);
};

export default Bspp;

+ 10
- 0
src/components/Bspp/BsppWrapper.tsx View File

@@ -0,0 +1,10 @@
import Bspp from "./Bspp";

const BsppWrapper: React.FC = async () => {

return (
<Bspp></Bspp>
);
};

export default BsppWrapper;

+ 1
- 0
src/components/Bspp/index.ts View File

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

+ 6
- 3
src/components/CreateProject/CreateProject.tsx View File

@@ -277,6 +277,7 @@ const CreateProject: React.FC<Props> = ({
projectName: mainProjects !== undefined ? mainProjects[0].projectName : undefined,
projectDescription: mainProjects !== undefined ? mainProjects[0].projectDescription : undefined,
expectedProjectFee: mainProjects !== undefined ? mainProjects[0].expectedProjectFee : undefined,
clientId: allCustomers !== undefined ? allCustomers[0].id : undefined,
...defaultInputs,

// manhourPercentageByGrade should have a sensible default
@@ -430,9 +431,11 @@ const CreateProject: React.FC<Props> = ({
startIcon={<Check />}
type="submit"
disabled={
formProps.getValues("projectDeleted") === true || formProps.getValues("projectStatus") === "Deleted" ||
(!!formProps.getValues("projectActualStart") &&
!!formProps.getValues("projectActualEnd"))
formProps.getValues("projectDeleted") === true || formProps.getValues("projectStatus").toLowerCase() === "deleted" ||
(
// !!formProps.getValues("projectActualStart") &&
!!(formProps.getValues("projectStatus").toLowerCase() === "completed")
)
}
>
{isEditMode ? t("Save") : t("Confirm")}


+ 1
- 1
src/components/CreateProject/StaffAllocation.tsx View File

@@ -118,7 +118,7 @@ const StaffAllocation: React.FC<Props> = ({
},
{ label: t("Team"), name: "team" },
{ label: t("Grade"), name: "grade" },
{ label: t("Staff ID"), name: "id" },
{ label: t("Staff ID"), name: "staffId" },
{ label: t("Staff Name"), name: "name" },
{ label: t("Title"), name: "currentPosition" },
],


+ 2
- 10
src/components/CreateStaff/CreateStaff.tsx View File

@@ -80,7 +80,7 @@ const CreateStaff: React.FC<formProps> = ({ combos }) => {
haveError = true
formProps.setError("phone1", { message: t("Please Enter Correct Phone No.."), type: "required" })
}
if(!regex_phone.test(data.emergContactPhone)) {
if(data.emergContactPhone && !regex_phone.test(data.emergContactPhone)) {
haveError = true
formProps.setError("emergContactPhone", { message: t("Please Enter Correct Phone No.."), type: "required" })
}
@@ -110,19 +110,11 @@ const CreateStaff: React.FC<formProps> = ({ combos }) => {
haveError = true
formProps.setError("employType", { message: t("Please Enter Employ Type."), type: "required" })
}
if (!data.departmentId) {
haveError = true
formProps.setError("departmentId", { message: t("Please Enter Department."), type: "required" })
}
if (!data.salaryId) {
haveError = true
formProps.setError("salaryId", { message: t("Please Enter Salary."), type: "required" })
}
if (!data.joinDate) {
haveError = true
formProps.setError("joinDate", { message: t("Please Enter Join Date."), type: "required" })
}
if (data.departDate && new Date(data.departDate) <= new Date(data.joinDate)) {
if (data.joinDate &&data.departDate && new Date(data.departDate) <= new Date(data.joinDate)) {
haveError = true
formProps.setError("departDate", { message: t("Depart Date cannot be earlier than Join Date."), type: "required" })
}


+ 13
- 9
src/components/CreateStaff/StaffInfo.tsx View File

@@ -165,7 +165,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
</Grid>
<Grid item xs={6}>
<FormControl fullWidth>
<InputLabel required>{t("Department")}</InputLabel>
<InputLabel>{t("Department")}</InputLabel>
<Controller
control={control}
name="departmentId"
@@ -377,9 +377,11 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
label={t("Emergency Contact Name")}
fullWidth
required
{...register("emergContactName", {
required: "Emergency Contact Name required!",
})}
{...register("emergContactName"
// , {
// required: "Emergency Contact Name required!",
// }
)}
error={Boolean(errors.emergContactName)}
helperText={
Boolean(errors.emergContactName) &&
@@ -394,9 +396,11 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
label={t("Emergency Contact Phone")}
fullWidth
required
{...register("emergContactPhone", {
required: "Emergency Contact Phone required!",
})}
{...register("emergContactPhone"
// , {
// required: "Emergency Contact Phone required!",
// }
)}
error={Boolean(errors.emergContactPhone)}
helperText={
Boolean(errors.emergContactPhone) &&
@@ -421,7 +425,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
}}
slotProps={{
textField: {
required: true,
// required: true,
error:
joinDate === "Invalid Date" || Boolean(errors.joinDate),
// value: errors.joinDate?.message,
@@ -432,7 +436,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
</Grid>
<Grid item xs={6}>
<FormControl fullWidth>
<InputLabel required>{t("Join Position")}</InputLabel>
<InputLabel>{t("Join Position")}</InputLabel>
<Controller
control={control}
name="joinPositionId"


+ 2
- 2
src/components/EditStaff/EditStaff.tsx View File

@@ -108,7 +108,7 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos }) => {
haveError = true
formProps.setError("phone1", { message: t("Please Enter Correct Phone No.."), type: "required" })
}
if(!regex_phone.test(data.emergContactPhone)) {
if(data.emergContactPhone && !regex_phone.test(data.emergContactPhone)) {
haveError = true
formProps.setError("emergContactPhone", { message: t("Please Enter Correct Phone No.."), type: "required" })
}
@@ -150,7 +150,7 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos }) => {
haveError = true
formProps.setError("joinDate", { message: t("Please Enter Join Date."), type: "required" })
}
if (data.departDate && new Date(data.departDate) <= new Date(data.joinDate)) {
if (data.joinDate &&data.departDate && new Date(data.departDate) <= new Date(data.joinDate)) {
haveError = true
formProps.setError("departDate", { message: t("Depart Date cannot be earlier than Join Date."), type: "required" })
}


+ 2
- 2
src/components/EditStaff/StaffInfo.tsx View File

@@ -417,7 +417,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
}}
slotProps={{
textField: {
required: true,
// required: true,
error:
joinDate === "Invalid Date" || Boolean(errors.joinDate),
// value: errors.joinDate?.message,
@@ -466,7 +466,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
}}
slotProps={{
textField: {
error: departDate
error: joinDate && departDate
? new Date(joinDate) > new Date(departDate)
: false,
},


+ 1
- 1
src/components/EditStaffForm/EditStaffForm.tsx View File

@@ -54,7 +54,7 @@ const EditStaffForm: React.FC<formProps> = ({ Title, fieldLists }) => {
const temp = {
id: parseInt(idString),
...data,
emergContactPhone: data.emergContactPhone.toString(),
emergContactPhone: data.emergContactPhone ? data.emergContactPhone.toString() : "",
phone1: data.phone1.toString(),
phone2: data.phone1.toString(),
joinDate: formatJoinDate,


+ 1
- 1
src/components/EditTeam/EditTeam.tsx View File

@@ -175,7 +175,7 @@ const EditTeam: React.FC<Props> = async ({ staff, teamInfo }) => {
<Button
variant="outlined"
startIcon={<Close />}
// onClick={handleCancel}
onClick={() => router.back()}
>
{t("Cancel")}
</Button>


+ 4
- 2
src/components/EditUser/EditUser.tsx View File

@@ -50,6 +50,7 @@ interface Props {
}

const EditUser: React.FC<Props> = async ({ user, rules, auths }) => {
console.log(user)
const { t } = useTranslation("user");
const formProps = useForm<UserInputs>();
const searchParams = useSearchParams();
@@ -79,7 +80,7 @@ const EditUser: React.FC<Props> = async ({ user, rules, auths }) => {
console.log(addAuthIds);
try {
formProps.reset({
name: user.username,
username: user.username,
email: user.email,
addAuthIds: addAuthIds,
removeAuthIds: [],
@@ -145,7 +146,8 @@ const EditUser: React.FC<Props> = async ({ user, rules, auths }) => {
}
}
const userData = {
name: data.name,
username: data.username,
name: user.name,
locked: false,
addAuthIds: data.addAuthIds || [],
removeAuthIds: data.removeAuthIds || [],


+ 8
- 6
src/components/EditUser/EditUserWrapper.tsx View File

@@ -15,14 +15,16 @@ interface SubComponents {
}

const EditUserWrapper: React.FC<searchParamsProps> & SubComponents = async ({
searchParams
searchParams,
}) => {
const id = parseInt(searchParams.id as string)
const pwRule = await fetchPwRules()
const user = await fetchUserDetails(id);
const auths = await fetchAuth("user", id);
const id = parseInt(searchParams.id as string);
const [pwRule, user, auths] = await Promise.all([
fetchPwRules(),
fetchUserDetails(id),
fetchAuth("user", id),
]);

return <EditUser user={user.data} rules={pwRule} auths={auths.records}/>
return <EditUser user={user.data} rules={pwRule} auths={auths.records} />;
};

EditUserWrapper.Loading = EditUserLoading;


+ 2
- 2
src/components/EditUser/UserDetail.tsx View File

@@ -34,10 +34,10 @@ const UserDetail: React.FC = () => {
<TextField
label={t("username")}
fullWidth
{...register("name", {
{...register("username", {
required: "username required!",
})}
error={Boolean(errors.name)}
error={Boolean(errors.username)}
/>
</Grid>
<Grid item xs={6}>


+ 28
- 24
src/components/EditUserGroup/EditUserGroup.tsx View File

@@ -1,6 +1,6 @@
"use client";
import { useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useMemo, useState } from "react";
import React, { useCallback, useEffect, useMemo, useState } from "react";
import SearchResults, { Column } from "../SearchResults";
// import { TeamResult } from "@/app/api/team";
import { useTranslation } from "react-i18next";
@@ -13,22 +13,22 @@ import {
useForm,
useFormContext,
} from "react-hook-form";
import { Check, Close, Error } from "@mui/icons-material";
import { Check, Close, Error, RestartAlt } from "@mui/icons-material";
import { StaffResult } from "@/app/api/staff";
import { CreateGroupInputs, auth, fetchAuth, saveGroup } from "@/app/api/group/actions";
import { UserGroupResult } from "@/app/api/group";
import { IndivUserGroup, UserGroupResult } from "@/app/api/group";
import { UserResult } from "@/app/api/user";
import GroupInfo from "./GroupInfo";
import AuthorityAllocation from "./AuthorityAllocation";
import UserAllocation from "./UserAllocation";
interface Props {
groups: UserGroupResult[];
// auths: auth[];
// groups: UserGroupResult[];
auths: auth[];
users: UserResult[];
group: IndivUserGroup
}

const EditUserGroup: React.FC<Props> = ({ groups, users }) => {
// console.log(users)
const EditUserGroup: React.FC<Props> = ({ users, auths, group }) => {
const { t } = useTranslation();
const [serverError, setServerError] = useState("");
const formProps = useForm<CreateGroupInputs>();
@@ -36,7 +36,6 @@ const EditUserGroup: React.FC<Props> = ({ groups, users }) => {
const id = parseInt(searchParams.get("id") || "0");
const router = useRouter();
const [tabIndex, setTabIndex] = useState(0);
const [auths, setAuths] = useState<auth[]>();

const errors = formProps.formState.errors;

@@ -79,22 +78,20 @@ const EditUserGroup: React.FC<Props> = ({ groups, users }) => {
},
[router]
);

const resetGroup = React.useCallback(() => {
formProps.reset({
name: group.data.name,
description: group.data.description,
addAuthIds: group.authIds,
addUserIds: group.userIds,
})
}, [group, users]);

useEffect(() => {
const thisGroup = groups.filter((item) => item.id === id)[0];
const addUserIds = users.filter((item) => item.groupId === id).map((data) => data.id)
let addAuthIds: number[] = []
fetchAuth("group", id).then((data) => {
setAuths(data.records)
addAuthIds = data.records.filter((data) => data.v === 1).map((data) => data.id).sort((a, b) => a - b);
formProps.reset({
name: thisGroup.name,
description: thisGroup.description,
addAuthIds: addAuthIds,
addUserIds: addUserIds,
});
});
// console.log(auths)
}, [groups, users]);
resetGroup()
}, []);
// }, [group, users]);

return (
<>
@@ -140,10 +137,17 @@ const EditUserGroup: React.FC<Props> = ({ groups, users }) => {
{tabIndex === 1 && <AuthorityAllocation auth={auths!!}/>}
{tabIndex === 2 && <UserAllocation users={users!!} />}
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button
variant="text"
startIcon={<RestartAlt />}
onClick={resetGroup}
>
{t("Reset")}
</Button>
<Button
variant="outlined"
startIcon={<Close />}
// onClick={handleCancel}
onClick={() => router.back()}
>
{t("Cancel")}
</Button>


+ 12
- 5
src/components/EditUserGroup/EditUserGroupWrapper.tsx View File

@@ -1,24 +1,31 @@
import React from "react";
import EditUserGroup from "./EditUserGroup";
import EditUserGroupLoading from "./EditUserGroupLoading";
import { fetchGroup } from "@/app/api/group";
import { fetchGroup, fetchIndivGroup } from "@/app/api/group";
import { fetchUser } from "@/app/api/user";
import { fetchAuth } from "@/app/api/group/actions";

interface SubComponents {
Loading: typeof EditUserGroupLoading;
}

const EditUserGroupWrapper: React.FC & SubComponents = async () => {
interface Props {
id: number
}

const EditUserGroupWrapper: React.FC<Props> & SubComponents = async ({ id }) => {

const [
groups,
group,
users,
auths,
] = await Promise.all([
fetchGroup(),
fetchIndivGroup(id),
fetchUser(),
fetchAuth("group", id),
]);

return <EditUserGroup groups={groups.records} users={users}/>;
return <EditUserGroup users={users} auths={auths.records} group={group}/>;
};

EditUserGroupWrapper.Loading = EditUserGroupLoading;


+ 1
- 0
src/components/EditUserGroup/GroupInfo.tsx View File

@@ -43,6 +43,7 @@ const GroupInfo: React.FC = () => {
<TextField
label={t("Group Name")}
fullWidth
rows={4}
{...register("name", {
required: true,
})}


+ 9
- 3
src/components/GenerateMonthlyWorkHoursReport/GenerateMonthlyWorkHoursReport.tsx View File

@@ -49,11 +49,17 @@ const GenerateMonthlyWorkHoursReport: React.FC<Props> = ({ staffs }) => {
formType={"download"}
criteria={searchCriteria}
onSearch={async (query: any) => {
console.log(query);
const index = staffCombo.findIndex((staff) => staff === query.staff);
const response = await fetchMonthlyWorkHoursReport({
let postData = {
id: staffs[index].id,
yearMonth: query.date,
});
yearMonth: dayjs().format("YYYY-MM").toString(),
};
console.log(query.date.length > 0)
if (query.date.length > 0) {
postData.yearMonth = query.date
}
const response = await fetchMonthlyWorkHoursReport(postData);
if (response) {
downloadFile(
new Uint8Array(response.blobValue),


+ 6
- 1
src/components/NavigationContent/NavigationContent.tsx View File

@@ -63,7 +63,7 @@ const NavigationContent: React.FC<Props> = ({ abilities }) => {
{
icon: <WorkHistory />,
label: "User Workspace",
path: "/home",
path: "/home",
showOnMobile: true,
},
{
@@ -71,6 +71,11 @@ const NavigationContent: React.FC<Props> = ({ abilities }) => {
label: "Dashboard",
path: "",
children: [
{
icon: <WorkHistory />,
label: "BSPP",
path: "/dashboard/bspp",
},
{
icon: <SummarizeIcon />,
label: "Financial Summary",


Loading…
Cancel
Save