diff --git a/src/app/(main)/projects/create/sub/page.tsx b/src/app/(main)/projects/create/sub/page.tsx
index bd5837e..1a8fab7 100644
--- a/src/app/(main)/projects/create/sub/page.tsx
+++ b/src/app/(main)/projects/create/sub/page.tsx
@@ -1,6 +1,7 @@
import { fetchAllCustomers, fetchAllSubsidiaries } from "@/app/api/customer";
import { fetchGrades } from "@/app/api/grades";
import {
+ fetchMainProjects,
fetchProjectBuildingTypes,
fetchProjectCategories,
fetchProjectContractTypes,
@@ -12,10 +13,12 @@ import {
} from "@/app/api/projects";
import { preloadStaff, preloadTeamLeads } from "@/app/api/staff";
import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks";
+import { ServerFetchError } from "@/app/utils/fetchUtil";
import CreateProject from "@/components/CreateProject";
import { I18nProvider, getServerI18n } from "@/i18n";
import Typography from "@mui/material/Typography";
import { Metadata } from "next";
+import { notFound } from "next/navigation";
export const metadata: Metadata = {
title: "Create Project",
@@ -39,12 +42,21 @@ const Projects: React.FC = async () => {
fetchGrades();
preloadTeamLeads();
preloadStaff();
+ try {
+ const data = await fetchMainProjects();
+
+ if (!Boolean(data) || data.length === 0) {
+ notFound();
+ }
+ } catch (e) {
+ notFound();
+ }
return (
<>
{t("Create Sub Project")}
-
+
>
);
diff --git a/src/app/api/projects/actions.ts b/src/app/api/projects/actions.ts
index c1be476..97cb34e 100644
--- a/src/app/api/projects/actions.ts
+++ b/src/app/api/projects/actions.ts
@@ -22,6 +22,7 @@ export interface CreateProjectInputs {
projectActualEnd: string;
projectStatus: string;
isClpProject: boolean;
+ mainProjectId?: number | null;
// Project info
serviceTypeId: number;
diff --git a/src/app/api/projects/index.ts b/src/app/api/projects/index.ts
index 7a64ea1..d25ba16 100644
--- a/src/app/api/projects/index.ts
+++ b/src/app/api/projects/index.ts
@@ -15,6 +15,27 @@ export interface ProjectResult {
status: string;
}
+export interface MainProject {
+ projectId: number;
+ projectCode: string;
+ projectName: string;
+ projectCategoryId: number;
+ projectDescription: string;
+ projectLeadId: number;
+ projectStatus: string;
+ isClpProject: boolean;
+ serviceTypeId: number;
+ fundingTypeId: number;
+ contractTypeId: number;
+ locationId: number;
+ buildingTypeIds: number[];
+ workNatureIds: number[];
+ clientId: number;
+ clientContactId: number;
+ clientSubsidiaryId: number;
+ expectedProjectFee: number;
+}
+
export interface ProjectCategory {
id: number;
name: string;
@@ -82,7 +103,7 @@ export const fetchProjects = cache(async () => {
});
export const fetchMainProjects = cache(async () => {
- return serverFetchJson(`${BASE_API_URL}/projects/main`, {
+ return serverFetchJson(`${BASE_API_URL}/projects/main`, {
next: { tags: ["projects"] },
});
});
diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx
index 516aca2..8c1b9b0 100644
--- a/src/components/Breadcrumb/Breadcrumb.tsx
+++ b/src/components/Breadcrumb/Breadcrumb.tsx
@@ -15,7 +15,9 @@ const pathToLabelMap: { [path: string]: string } = {
"/home": "User Workspace",
"/projects": "Projects",
"/projects/create": "Create Project",
+ "/projects/create/sub": "Sub Project",
"/projects/edit": "Edit Project",
+ "/projects/edit/sub": "Sub Project",
"/tasks": "Task Template",
"/tasks/create": "Create Task Template",
"/staffReimbursement": "Staff Reimbursement",
diff --git a/src/components/ControlledAutoComplete/ControlledAutoComplete.tsx b/src/components/ControlledAutoComplete/ControlledAutoComplete.tsx
index d8a2962..e37f348 100644
--- a/src/components/ControlledAutoComplete/ControlledAutoComplete.tsx
+++ b/src/components/ControlledAutoComplete/ControlledAutoComplete.tsx
@@ -10,7 +10,7 @@ const icon = ;
const checkedIcon = ;
// label -> e.g. code - name -> 001 - WL
// name -> WL
-interface Props {
+interface Props {
control: Control,
options: T[],
name: Path, // register name
@@ -18,7 +18,6 @@ interface Props
- error?: boolean,
}
function ControlledAutoComplete<
@@ -28,68 +27,78 @@ function ControlledAutoComplete<
props: Props
) {
const { t } = useTranslation()
- const { control, options, name, label, noOptionsText, isMultiple, rules, error } = props;
+ const { control, options, name, label, noOptionsText, isMultiple, rules } = props;
+
+ // set default value if value is null
+ if (!Boolean(isMultiple) && !Boolean(control._formValues[name])) {
+ console.log(name, control._formValues[name])
+ control._formValues[name] = options[0]?.id ?? undefined
+ } else if (Boolean(isMultiple) && !Boolean(control._formValues[name])) {
+ control._formValues[name] = []
+ }
return (
(
- isMultiple ?
- {
- // console.log(field.value)
- return field.value?.includes(option.id)
- })}
- options={options}
- getOptionLabel={(option) => option.label ?? option.name!!}
- isOptionEqualToValue={(option, value) => option.id === value.id}
- renderOption={(params, option, { selected }) => {
- return (
-
-
- {option.label ?? option.name}
-
- );
- }}
- onChange={(event, value) => {
- field.onChange(value?.map(v => v.id))
- }}
- renderInput={(params) => }
- />
- :
- option.id === field.value) ?? options[0]}
- options={options}
- getOptionLabel={(option) => option.label ?? option.name!!}
- isOptionEqualToValue={(option, value) => option.id === value.id}
- renderOption={(params, option) => {
- return (
-
- );
- }}
- onChange={(event, value) => {
- field.onChange(value?.id)
- }}
- renderInput={(params) => }
- />
- )}
+
+ render={({ field, fieldState, formState }) => {
+
+ return (
+ isMultiple ?
+ {
+ return field.value?.includes(option.id)
+ })}
+ options={options}
+ getOptionLabel={(option) => option.label ?? option.name!!}
+ isOptionEqualToValue={(option, value) => option.id === value.id}
+ renderOption={(params, option, { selected }) => {
+ return (
+
+
+ {option.label ?? option.name}
+
+ );
+ }}
+ onChange={(event, value) => {
+ field.onChange(value?.map(v => v.id))
+ }}
+ renderInput={(params) => }
+ />
+ :
+ option.id === field.value) ?? options[0]}
+ options={options}
+ getOptionLabel={(option) => option.label ?? option.name!!}
+ isOptionEqualToValue={(option, value) => option?.id === value?.id}
+ renderOption={(params, option) => {
+ return (
+
+ );
+ }}
+ onChange={(event, value) => {
+ field.onChange(value?.id)
+ }}
+ renderInput={(params) => }
+ />)
+ }}
/>
)
}
diff --git a/src/components/CreateProject/CreateProject.tsx b/src/components/CreateProject/CreateProject.tsx
index 937bac5..5f35919 100644
--- a/src/components/CreateProject/CreateProject.tsx
+++ b/src/components/CreateProject/CreateProject.tsx
@@ -33,6 +33,7 @@ import {
ContractType,
FundingType,
LocationType,
+ MainProject,
ProjectCategory,
ServiceType,
WorkNature,
@@ -52,6 +53,8 @@ import dayjs from "dayjs";
export interface Props {
isEditMode: boolean;
+ isSubProject: boolean;
+ mainProjects?: MainProject[];
defaultInputs?: CreateProjectInputs;
allTasks: Task[];
projectCategories: ProjectCategory[];
@@ -93,6 +96,8 @@ const hasErrorsInTab = (
const CreateProject: React.FC = ({
isEditMode,
+ isSubProject,
+ mainProjects,
defaultInputs,
allTasks,
projectCategories,
@@ -269,14 +274,9 @@ const CreateProject: React.FC = ({
milestones: {},
totalManhour: 0,
taskTemplateId: "All",
- projectCategoryId: projectCategories.length > 0 ? projectCategories[0].id : undefined,
- projectLeadId: teamLeads.length > 0 ? teamLeads[0].id : undefined,
- serviceTypeId: serviceTypes.length > 0 ? serviceTypes[0].id : undefined,
- fundingTypeId: fundingTypes.length > 0 ? fundingTypes[0].id : undefined,
- contractTypeId: contractTypes.length > 0 ? contractTypes[0].id : undefined,
- locationId: locationTypes.length > 0 ? locationTypes[0].id : undefined,
- clientSubsidiaryId: undefined,
- clientId: allCustomers.length > 0 ? allCustomers[0].id : undefined,
+ projectName: mainProjects !== undefined ? mainProjects[0].projectName : undefined,
+ projectDescription: mainProjects !== undefined ? mainProjects[0].projectDescription : undefined,
+ expectedProjectFee: mainProjects !== undefined ? mainProjects[0].expectedProjectFee : undefined,
...defaultInputs,
// manhourPercentageByGrade should have a sensible default
@@ -380,6 +380,8 @@ const CreateProject: React.FC = ({
{
= async (props) => {
? await fetchProjectDetails(props.projectId!)
: undefined;
+ const mainProjects = Boolean(props.isSubProject)
+ ? await fetchMainProjects()
+ : undefined;
+
return (
= async (props) => {
workNatures={workNatures}
allStaffs={allStaffs}
grades={grades}
+ mainProjects={mainProjects}
/>
);
};
diff --git a/src/components/CreateProject/ProjectClientDetails.tsx b/src/components/CreateProject/ProjectClientDetails.tsx
index d77defe..8fb7fab 100644
--- a/src/components/CreateProject/ProjectClientDetails.tsx
+++ b/src/components/CreateProject/ProjectClientDetails.tsx
@@ -22,6 +22,7 @@ import {
ContractType,
FundingType,
LocationType,
+ MainProject,
ProjectCategory,
ServiceType,
WorkNature,
@@ -37,6 +38,8 @@ import ControlledAutoComplete from "../ControlledAutoComplete/ControlledAutoComp
interface Props {
isActive: boolean;
+ isSubProject: boolean;
+ mainProjects?: MainProject[];
projectCategories: ProjectCategory[];
teamLeads: StaffResult[];
allCustomers: Customer[];
@@ -51,6 +54,8 @@ interface Props {
const ProjectClientDetails: React.FC = ({
isActive,
+ isSubProject,
+ mainProjects,
projectCategories,
teamLeads,
allCustomers,
@@ -70,6 +75,8 @@ const ProjectClientDetails: React.FC = ({
control,
setValue,
getValues,
+ reset,
+ resetField,
} = useFormContext();
const subsidiaryMap = useMemo<{
@@ -136,7 +143,6 @@ const ProjectClientDetails: React.FC = ({
// Automatically add the team lead to the allocated staff list
const selectedTeamLeadId = watch("projectLeadId");
useEffect(() => {
- console.log(selectedTeamLeadId)
if (selectedTeamLeadId !== undefined) {
const currentStaffIds = getValues("allocatedStaffIds");
const newList = uniq([...currentStaffIds, selectedTeamLeadId]);
@@ -144,6 +150,32 @@ const ProjectClientDetails: React.FC = ({
}
}, [getValues, selectedTeamLeadId, setValue]);
+ // Automatically update the project & client details whene select a main project
+ const mainProjectId = watch("mainProjectId")
+ useEffect(() => {
+ if (mainProjectId !== undefined && mainProjects !== undefined) {
+ const mainProject = mainProjects.find(project => project.projectId === mainProjectId);
+
+ if (mainProject !== undefined) {
+ setValue("projectName", mainProject.projectName)
+ setValue("projectCategoryId", mainProject.projectCategoryId)
+ setValue("projectLeadId", mainProject.projectLeadId)
+ setValue("serviceTypeId", mainProject.serviceTypeId)
+ setValue("fundingTypeId", mainProject.fundingTypeId)
+ setValue("contractTypeId", mainProject.contractTypeId)
+ setValue("locationId", mainProject.locationId)
+ setValue("buildingTypeIds", mainProject.buildingTypeIds)
+ setValue("workNatureIds", mainProject.workNatureIds)
+ setValue("projectDescription", mainProject.projectDescription)
+ setValue("expectedProjectFee", mainProject.expectedProjectFee)
+ setValue("isClpProject", mainProject.isClpProject)
+ setValue("clientId", mainProject.clientId)
+ setValue("clientSubsidiaryId", mainProject.clientSubsidiaryId)
+ setValue("clientContactId", mainProject.clientContactId)
+ }
+ }
+ }, [getValues, mainProjectId, setValue])
+
// const buildingTypeIdNameMap = buildingTypes.reduce<{ [id: number]: string }>(
// (acc, building) => ({ ...acc, [building.id]: building.name }),
// {},
@@ -162,6 +194,18 @@ const ProjectClientDetails: React.FC = ({
{t("Project Details")}
+ {
+ isSubProject && mainProjects !== undefined && <>
+ ({ id: mainProject.projectId, label: `${mainProject.projectCode} - ${mainProject.projectName}` }))]}
+ name="mainProjectId"
+ label={t("Main Project")}
+ noOptionsText={t("No Main Project")}
+ />
+
+ >
+ }
= ({
{t("CLP Project")}
@@ -317,7 +361,6 @@ const ProjectClientDetails: React.FC = ({
rules={{
required: "Please select a client"
}}
- error={Boolean(errors.clientId)}
/>
@@ -337,7 +380,7 @@ const ProjectClientDetails: React.FC = ({
subsidiaryMap[subId])
.map((subsidiaryId, index) => {
const subsidiary = subsidiaryMap[subsidiaryId]
@@ -368,7 +411,6 @@ const ProjectClientDetails: React.FC = ({
} else return true;
},
}}
- error={Boolean(errors.clientContactId)}
/>
diff --git a/src/components/CreateProject/StaffAllocation.tsx b/src/components/CreateProject/StaffAllocation.tsx
index bd699ec..81d3c97 100644
--- a/src/components/CreateProject/StaffAllocation.tsx
+++ b/src/components/CreateProject/StaffAllocation.tsx
@@ -25,6 +25,7 @@ import {
Tab,
Tabs,
SelectChangeEvent,
+ Autocomplete,
} from "@mui/material";
import differenceWith from "lodash/differenceWith";
import intersectionWith from "lodash/intersectionWith";
diff --git a/src/components/CreateProject/TaskSetup.tsx b/src/components/CreateProject/TaskSetup.tsx
index dac0698..fe02f37 100644
--- a/src/components/CreateProject/TaskSetup.tsx
+++ b/src/components/CreateProject/TaskSetup.tsx
@@ -7,7 +7,7 @@ import Typography from "@mui/material/Typography";
import { useTranslation } from "react-i18next";
import TransferList from "../TransferList";
import Button from "@mui/material/Button";
-import React, { useCallback, useMemo, useState } from "react";
+import React, { SyntheticEvent, useCallback, useMemo, useState } from "react";
import CardActions from "@mui/material/CardActions";
import RestartAlt from "@mui/icons-material/RestartAlt";
import FormControl from "@mui/material/FormControl";
@@ -20,6 +20,7 @@ import { CreateProjectInputs, ManhourAllocation } from "@/app/api/projects/actio
import isNumber from "lodash/isNumber";
import intersectionWith from "lodash/intersectionWith";
import { difference } from "lodash";
+import { Autocomplete, TextField } from "@mui/material";
interface Props {
allTasks: Task[];
@@ -50,9 +51,9 @@ const TaskSetup: React.FC = ({
"All" | number
>(watch("taskTemplateId") ?? "All");
const onSelectTaskTemplate = useCallback(
- (e: SelectChangeEvent) => {
- if (e.target.value === "All" || isNumber(e.target.value)) {
- setSelectedTaskTemplateId(e.target.value);
+ (event: SyntheticEvent, value: NonNullable<{id: number | string, name: string}>) => {
+ if (value.id === "All" || isNumber(value.id)) {
+ setSelectedTaskTemplateId(value.id);
// onReset();
}
},
@@ -132,21 +133,24 @@ const TaskSetup: React.FC = ({
marginBlockEnd={1}
>
-
- {t("Task List Source")}
-
-
+ );
+ }}
+ onChange={onSelectTaskTemplate}
+ renderInput={(params) => }
+ />