Sfoglia il codice sorgente

Allow changing client type

tags/Baseline_30082024_FRONTEND_UAT
Wayne 1 anno fa
parent
commit
86dfc30972
5 ha cambiato i file con 354 aggiunte e 174 eliminazioni
  1. +4
    -3
      src/app/(main)/projects/create/page.tsx
  2. +7
    -6
      src/app/api/projects/actions.ts
  3. +179
    -71
      src/components/CreateProject/CreateProject.tsx
  4. +8
    -1
      src/components/CreateProject/CreateProjectWrapper.tsx
  5. +156
    -93
      src/components/CreateProject/ProjectClientDetails.tsx

+ 4
- 3
src/app/(main)/projects/create/page.tsx Vedi File

@@ -1,4 +1,4 @@
import { fetchAllCustomers, fetchAllSubsidiaries } from "@/app/api/customer";
import { fetchAllCustomers, fetchAllSubsidiaries, fetchCustomerTypes } from "@/app/api/customer";
import { fetchGrades } from "@/app/api/grades";
import {
fetchProjectBuildingTypes,
@@ -26,9 +26,9 @@ export const metadata: Metadata = {
const Projects: React.FC = async () => {
const { t } = await getServerI18n("projects");

const abilities = await getUserAbilities()
const abilities = await getUserAbilities();

if (![MAINTAIN_PROJECT].some(ability => abilities.includes(ability))) {
if (![MAINTAIN_PROJECT].some((ability) => abilities.includes(ability))) {
notFound();
}

@@ -44,6 +44,7 @@ const Projects: React.FC = async () => {
fetchProjectWorkNatures();
fetchAllCustomers();
fetchAllSubsidiaries();
fetchCustomerTypes();
fetchGrades();
preloadTeamLeads();
preloadStaff();


+ 7
- 6
src/app/api/projects/actions.ts Vedi File

@@ -40,6 +40,7 @@ export interface CreateProjectInputs {
clientSubsidiaryId?: number | null;
subsidiaryContactId?: number;
isSubsidiaryContact?: boolean;
clientTypeId?: number;

// Allocation
totalManhour: number;
@@ -117,12 +118,12 @@ export const deleteProject = async (id: number) => {
};

export const importProjects = async (data: FormData) => {
const importProjects = await serverFetchString<String>(
`${BASE_API_URL}/projects/import`,
{
method: "POST",
body: data,
},
const importProjects = await serverFetchString<string>(
`${BASE_API_URL}/projects/import`,
{
method: "POST",
body: data,
},
);

return importProjects;


+ 179
- 71
src/components/CreateProject/CreateProject.tsx Vedi File

@@ -41,7 +41,7 @@ import {
import { StaffResult } from "@/app/api/staff";
import { Typography } from "@mui/material";
import { Grade } from "@/app/api/grades";
import { Customer, Subsidiary } from "@/app/api/customer";
import { Customer, CustomerType, Subsidiary } from "@/app/api/customer";
import { isEmpty } from "lodash";
import {
deleteDialog,
@@ -70,6 +70,7 @@ export interface Props {
buildingTypes: BuildingType[];
workNatures: WorkNature[];
allStaffs: StaffResult[];
customerTypes: CustomerType[];
grades: Grade[];
abilities: string[];
}
@@ -81,16 +82,19 @@ const hasErrorsInTab = (
switch (tabIndex) {
case 0:
return (
errors.projectName || errors.projectDescription || errors.clientId || errors.projectCode
errors.projectName ||
errors.projectDescription ||
errors.clientId ||
errors.projectCode
);
case 2:
return (
errors.totalManhour || errors.manhourPercentageByGrade || errors.taskGroups
errors.totalManhour ||
errors.manhourPercentageByGrade ||
errors.taskGroups
);
case 3:
return (
errors.milestones
)
return errors.milestones;
default:
false;
}
@@ -115,6 +119,7 @@ const CreateProject: React.FC<Props> = ({
buildingTypes,
workNatures,
allStaffs,
customerTypes,
abilities,
}) => {
const [serverError, setServerError] = useState("");
@@ -151,53 +156,102 @@ const CreateProject: React.FC<Props> = ({
console.log(data);

// detect errors
let hasErrors = false
let hasErrors = false;

// Tab - Staff Allocation and Resource
if (data.totalManhour === null || data.totalManhour <= 0) {
formProps.setError("totalManhour", { message: "totalManhour value is not valid", type: "required" })
setTabIndex(2)
hasErrors = true
formProps.setError("totalManhour", {
message: "totalManhour value is not valid",
type: "required",
});
setTabIndex(2);
hasErrors = true;
}

const manhourPercentageByGradeKeys = Object.keys(data.manhourPercentageByGrade)
if (manhourPercentageByGradeKeys.filter(k => data.manhourPercentageByGrade[k as any] < 0).length > 0 ||
manhourPercentageByGradeKeys.reduce((acc, value) => acc + data.manhourPercentageByGrade[value as any], 0) !== 100) {
formProps.setError("manhourPercentageByGrade", { message: "manhourPercentageByGrade value is not valid", type: "invalid" })
setTabIndex(2)
hasErrors = true
const manhourPercentageByGradeKeys = Object.keys(
data.manhourPercentageByGrade,
);
if (
manhourPercentageByGradeKeys.filter(
(k) => data.manhourPercentageByGrade[k as any] < 0,
).length > 0 ||
manhourPercentageByGradeKeys.reduce(
(acc, value) => acc + data.manhourPercentageByGrade[value as any],
0,
) !== 100
) {
formProps.setError("manhourPercentageByGrade", {
message: "manhourPercentageByGrade value is not valid",
type: "invalid",
});
setTabIndex(2);
hasErrors = true;
}

const taskGroupKeys = Object.keys(data.taskGroups)
if (taskGroupKeys.filter(k => data.taskGroups[k as any].percentAllocation < 0).length > 0 ||
taskGroupKeys.reduce((acc, value) => acc + data.taskGroups[value as any].percentAllocation, 0) !== 100) {
formProps.setError("taskGroups", { message: "Task Groups value is not invalid", type: "invalid" })
setTabIndex(2)
hasErrors = true
const taskGroupKeys = Object.keys(data.taskGroups);
if (
taskGroupKeys.filter(
(k) => data.taskGroups[k as any].percentAllocation < 0,
).length > 0 ||
taskGroupKeys.reduce(
(acc, value) =>
acc + data.taskGroups[value as any].percentAllocation,
0,
) !== 100
) {
formProps.setError("taskGroups", {
message: "Task Groups value is not invalid",
type: "invalid",
});
setTabIndex(2);
hasErrors = true;
}

// Tab - Milestone
let projectTotal = 0
const milestonesKeys = Object.keys(data.milestones).filter(key => taskGroupKeys.includes(key))
milestonesKeys.filter(key => Object.keys(data.taskGroups).includes(key)).forEach(key => {
const { startDate, endDate, payments } = data.milestones[parseFloat(key)]
let projectTotal = 0;
const milestonesKeys = Object.keys(data.milestones).filter((key) =>
taskGroupKeys.includes(key),
);
milestonesKeys
.filter((key) => Object.keys(data.taskGroups).includes(key))
.forEach((key) => {
const { startDate, endDate, payments } =
data.milestones[parseFloat(key)];

if (!Boolean(startDate) || startDate === "Invalid Date" || !Boolean(endDate) || endDate === "Invalid Date" || new Date(startDate) > new Date(endDate)) {
formProps.setError("milestones", { message: "milestones is not valid", type: "invalid" })
setTabIndex(3)
hasErrors = true
}
if (
!Boolean(startDate) ||
startDate === "Invalid Date" ||
!Boolean(endDate) ||
endDate === "Invalid Date" ||
new Date(startDate) > new Date(endDate)
) {
formProps.setError("milestones", {
message: "milestones is not valid",
type: "invalid",
});
setTabIndex(3);
hasErrors = true;
}

projectTotal += payments.reduce((acc, payment) => acc + payment.amount, 0)
})
projectTotal += payments.reduce(
(acc, payment) => acc + payment.amount,
0,
);
});

if (projectTotal !== data.expectedProjectFee || milestonesKeys.length !== taskGroupKeys.length) {
formProps.setError("milestones", { message: "milestones is not valid", type: "invalid" })
setTabIndex(3)
hasErrors = true
if (
projectTotal !== data.expectedProjectFee ||
milestonesKeys.length !== taskGroupKeys.length
) {
formProps.setError("milestones", {
message: "milestones is not valid",
type: "invalid",
});
setTabIndex(3);
hasErrors = true;
}

if (hasErrors) return false
if (hasErrors) return false;
// save project
setServerError("");

@@ -227,18 +281,29 @@ const CreateProject: React.FC<Props> = ({
data.projectActualEnd = dayjs().format("YYYY-MM-DD");
}

data.taskTemplateId = data.taskTemplateId === "All" ? undefined : data.taskTemplateId;
data.taskTemplateId =
data.taskTemplateId === "All" ? undefined : data.taskTemplateId;
const response = await saveProject(data);

if (response.id > 0 && response.message?.toLowerCase() === "success" && response.errorPosition === null) {
if (
response.id > 0 &&
response.message?.toLowerCase() === "success" &&
response.errorPosition === null
) {
successDialog(successTitle, t).then(() => {
router.replace("/projects");
});
} else {
errorDialog(response.message ?? errorTitle, t).then(() => {
if (response.errorPosition !== null && response.errorPosition === "projectCode") {
formProps.setError("projectCode", { message: response.message, type: "invalid" })
setTabIndex(0)
if (
response.errorPosition !== null &&
response.errorPosition === "projectCode"
) {
formProps.setError("projectCode", {
message: response.message,
type: "invalid",
});
setTabIndex(0);
}

return false;
@@ -257,7 +322,7 @@ const CreateProject: React.FC<Props> = ({

const onSubmitError = useCallback<SubmitErrorHandler<CreateProjectInputs>>(
(errors) => {
console.log(errors)
console.log(errors);
// Set the tab so that the focus will go there
if (
errors.projectName ||
@@ -266,10 +331,14 @@ const CreateProject: React.FC<Props> = ({
errors.clientId
) {
setTabIndex(0);
} else if (errors.totalManhour || errors.manhourPercentageByGrade || errors.taskGroups) {
setTabIndex(2)
} else if (
errors.totalManhour ||
errors.manhourPercentageByGrade ||
errors.taskGroups
) {
setTabIndex(2);
} else if (errors.milestones) {
setTabIndex(3)
setTabIndex(3);
}
},
[],
@@ -282,18 +351,26 @@ const CreateProject: React.FC<Props> = ({
milestones: {},
totalManhour: 0,
taskTemplateId: "All",
projectName: mainProjects !== undefined ? mainProjects[0].projectName : undefined,
projectDescription: mainProjects !== undefined ? mainProjects[0].projectDescription : undefined,
expectedProjectFee: mainProjects !== undefined ? mainProjects[0].expectedProjectFee : undefined,
subContractFee: mainProjects !== undefined ? mainProjects[0].subContractFee : undefined,
projectName:
mainProjects !== undefined ? mainProjects[0].projectName : undefined,
projectDescription:
mainProjects !== undefined
? mainProjects[0].projectDescription
: undefined,
expectedProjectFee:
mainProjects !== undefined
? mainProjects[0].expectedProjectFee
: undefined,
subContractFee:
mainProjects !== undefined ? mainProjects[0].subContractFee : undefined,
clientId: allCustomers !== undefined ? allCustomers[0].id : undefined,
...defaultInputs,

// manhourPercentageByGrade should have a sensible default
manhourPercentageByGrade: isEmpty(defaultInputs?.manhourPercentageByGrade)
? grades.reduce((acc, grade) => {
return { ...acc, [grade.id]: 100 / grades.length };
}, {})
return { ...acc, [grade.id]: 100 / grades.length };
}, {})
: defaultInputs?.manhourPercentageByGrade,
},
});
@@ -311,7 +388,8 @@ const CreateProject: React.FC<Props> = ({
{isEditMode && !(formProps.getValues("projectDeleted") === true) && (
<Stack direction="row" gap={1}>
{/* {!formProps.getValues("projectActualStart") && ( */}
{formProps.getValues("projectStatus")?.toLowerCase() === "pending to start" && (
{formProps.getValues("projectStatus")?.toLowerCase() ===
"pending to start" && (
<Button
name="start"
type="submit"
@@ -324,7 +402,8 @@ const CreateProject: React.FC<Props> = ({
)}
{/* {formProps.getValues("projectActualStart") &&
!formProps.getValues("projectActualEnd") && ( */}
{formProps.getValues("projectStatus")?.toLowerCase() === "on-going" && (
{formProps.getValues("projectStatus")?.toLowerCase() ===
"on-going" && (
<Button
name="complete"
type="submit"
@@ -338,9 +417,14 @@ const CreateProject: React.FC<Props> = ({
{!(
// formProps.getValues("projectActualStart") &&
// formProps.getValues("projectActualEnd")
formProps.getValues("projectStatus")?.toLowerCase() === "completed" ||
formProps.getValues("projectStatus")?.toLowerCase() === "deleted"
) && abilities.includes(DELETE_PROJECT) && (
(
formProps.getValues("projectStatus")?.toLowerCase() ===
"completed" ||
formProps.getValues("projectStatus")?.toLowerCase() ===
"deleted"
)
) &&
abilities.includes(DELETE_PROJECT) && (
<Button
variant="outlined"
startIcon={<Delete />}
@@ -359,7 +443,13 @@ const CreateProject: React.FC<Props> = ({
>
<Tab
label={t("Project and Client Details")}
sx={{ marginInlineEnd: !hasErrorsInTab(1, errors) && (hasErrorsInTab(2, errors) || hasErrorsInTab(3, errors)) ? 1 : undefined }}
sx={{
marginInlineEnd:
!hasErrorsInTab(1, errors) &&
(hasErrorsInTab(2, errors) || hasErrorsInTab(3, errors))
? 1
: undefined,
}}
icon={
hasErrorsInTab(0, errors) ? (
<Error sx={{ marginInlineEnd: 1 }} color="error" />
@@ -369,11 +459,22 @@ const CreateProject: React.FC<Props> = ({
/>
<Tab
label={t("Project Task Setup")}
sx={{ marginInlineEnd: hasErrorsInTab(2, errors) || hasErrorsInTab(3, errors) ? 1 : undefined }}
iconPosition="end" />
sx={{
marginInlineEnd:
hasErrorsInTab(2, errors) || hasErrorsInTab(3, errors)
? 1
: undefined,
}}
iconPosition="end"
/>
<Tab
label={t("Staff Allocation and Resource")}
sx={{ marginInlineEnd: !hasErrorsInTab(2, errors) && hasErrorsInTab(3, errors) ? 1 : undefined }}
sx={{
marginInlineEnd:
!hasErrorsInTab(2, errors) && hasErrorsInTab(3, errors)
? 1
: undefined,
}}
icon={
hasErrorsInTab(2, errors) ? (
<Error sx={{ marginInlineEnd: 1 }} color="error" />
@@ -381,12 +482,15 @@ const CreateProject: React.FC<Props> = ({
}
iconPosition="end"
/>
<Tab label={t("Milestone")}
<Tab
label={t("Milestone")}
icon={
hasErrorsInTab(3, errors) ? (
<Error sx={{ marginInlineEnd: 1 }} color="error" />)
: undefined}
iconPosition="end" />
<Error sx={{ marginInlineEnd: 1 }} color="error" />
) : undefined
}
iconPosition="end"
/>
</Tabs>
{
<ProjectClientDetails
@@ -401,6 +505,7 @@ const CreateProject: React.FC<Props> = ({
allCustomers={allCustomers}
allSubsidiaries={allSubsidiaries}
projectCategories={projectCategories}
customerTypes={customerTypes}
teamLeads={teamLeads}
isActive={tabIndex === 0}
isEditMode={isEditMode}
@@ -440,11 +545,14 @@ const CreateProject: React.FC<Props> = ({
startIcon={<Check />}
type="submit"
disabled={
formProps.getValues("projectDeleted") === true || formProps.getValues("projectStatus")?.toLowerCase() === "deleted" ||
(
// !!formProps.getValues("projectActualStart") &&
!!(formProps.getValues("projectStatus")?.toLowerCase() === "completed")
)
formProps.getValues("projectDeleted") === true ||
formProps.getValues("projectStatus")?.toLowerCase() ===
"deleted" ||
// !!formProps.getValues("projectActualStart") &&
!!(
formProps.getValues("projectStatus")?.toLowerCase() ===
"completed"
)
}
>
{isEditMode ? t("Save") : t("Confirm")}


+ 8
- 1
src/components/CreateProject/CreateProjectWrapper.tsx Vedi File

@@ -12,7 +12,11 @@ import {
fetchProjectWorkNatures,
} from "@/app/api/projects";
import { fetchStaff, fetchTeamLeads } from "@/app/api/staff";
import { fetchAllCustomers, fetchAllSubsidiaries } from "@/app/api/customer";
import {
fetchAllCustomers,
fetchAllSubsidiaries,
fetchCustomerTypes,
} from "@/app/api/customer";
import { fetchGrades } from "@/app/api/grades";
import { getUserAbilities } from "@/app/utils/commonUtil";

@@ -44,6 +48,7 @@ const CreateProjectWrapper: React.FC<Props> = async (props) => {
workNatures,
allStaffs,
grades,
customerTypes,
abilities,
] = await Promise.all([
fetchAllTasks(),
@@ -60,6 +65,7 @@ const CreateProjectWrapper: React.FC<Props> = async (props) => {
fetchProjectWorkNatures(),
fetchStaff(),
fetchGrades(),
fetchCustomerTypes(),
getUserAbilities(),
]);

@@ -90,6 +96,7 @@ const CreateProjectWrapper: React.FC<Props> = async (props) => {
workNatures={workNatures}
allStaffs={allStaffs}
grades={grades}
customerTypes={customerTypes}
mainProjects={mainProjects}
abilities={abilities}
/>


+ 156
- 93
src/components/CreateProject/ProjectClientDetails.tsx Vedi File

@@ -4,18 +4,12 @@ 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 FormControl from "@mui/material/FormControl";
import Grid from "@mui/material/Grid";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
import Select from "@mui/material/Select";
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 { useFormContext } from "react-hook-form";
import { CreateProjectInputs } from "@/app/api/projects/actions";
import {
BuildingType,
@@ -28,11 +22,15 @@ import {
WorkNature,
} from "@/app/api/projects";
import { StaffResult } from "@/app/api/staff";
import { Contact, Customer, Subsidiary } from "@/app/api/customer";
import {
Contact,
Customer,
CustomerType,
Subsidiary,
} from "@/app/api/customer";
import Link from "next/link";
import React, { useEffect, useMemo, useState } from "react";
import { fetchCustomer } from "@/app/api/customer/actions";
import { Autocomplete, Checkbox, ListItemText } from "@mui/material";
import uniq from "lodash/uniq";
import ControlledAutoComplete from "../ControlledAutoComplete/ControlledAutoComplete";

@@ -51,6 +49,7 @@ interface Props {
locationTypes: LocationType[];
buildingTypes: BuildingType[];
workNatures: WorkNature[];
customerTypes: CustomerType[];
}

const ProjectClientDetails: React.FC<Props> = ({
@@ -67,6 +66,7 @@ const ProjectClientDetails: React.FC<Props> = ({
fundingTypes,
locationTypes,
buildingTypes,
customerTypes,
workNatures,
}) => {
const { t } = useTranslation();
@@ -89,10 +89,6 @@ const ProjectClientDetails: React.FC<Props> = ({
);

const selectedCustomerId = watch("clientId");
const selectedCustomer = useMemo(
() => allCustomers.find((c) => c.id === selectedCustomerId),
[allCustomers, selectedCustomerId],
);

const [customerContacts, setCustomerContacts] = useState<Contact[]>([]);
const [subsidiaryContacts, setSubsidiaryContacts] = useState<Contact[]>([]);
@@ -103,44 +99,77 @@ const ProjectClientDetails: React.FC<Props> = ({
const selectedCustomerContactId = watch("clientContactId");
const selectedCustomerContact = useMemo(
() =>
subsidiaryContacts.length > 0 ?
subsidiaryContacts.find((contact) => contact.id === selectedCustomerContactId)
subsidiaryContacts.length > 0
? subsidiaryContacts.find(
(contact) => contact.id === selectedCustomerContactId,
)
: customerContacts.find(
(contact) => contact.id === selectedCustomerContactId,
),
(contact) => contact.id === selectedCustomerContactId,
),
[subsidiaryContacts, customerContacts, selectedCustomerContactId],
);

// get customer (client) contact combo
const clientSubsidiaryId = watch("clientSubsidiaryId")
const [firstCustomerLoaded, setFirstCustomerLoaded] = useState(false)
const clientSubsidiaryId = watch("clientSubsidiaryId");
const [firstCustomerLoaded, setFirstCustomerLoaded] = useState(false);
useEffect(() => {
if (selectedCustomerId !== undefined) {
fetchCustomer(selectedCustomerId).then(({ contacts, subsidiaryIds }) => {
setCustomerContacts(contacts);
setCustomerSubsidiaryIds(subsidiaryIds);
fetchCustomer(selectedCustomerId).then(
({ contacts, subsidiaryIds, customer }) => {
setCustomerContacts(contacts);
setCustomerSubsidiaryIds(subsidiaryIds);

if (isEditMode && firstCustomerLoaded) {
setValue("clientTypeId", customer.customerType.id);
}

// if (subsidiaryIds.length > 0) setValue("clientSubsidiaryId", subsidiaryIds[0])
// else
if (isEditMode && !firstCustomerLoaded) { setFirstCustomerLoaded(true) }
else if (subsidiaryIds.length > 0) setValue("clientSubsidiaryId", clientSubsidiaryId !== undefined && clientSubsidiaryId !== null ? subsidiaryIds.includes(clientSubsidiaryId) ? clientSubsidiaryId : null : null)
// if (contacts.length > 0) setValue("clientContactId", contacts[0].id)
// else setValue("clientContactId", undefined)
});
if (isEditMode && !firstCustomerLoaded) {
setFirstCustomerLoaded(true);
} else if (subsidiaryIds.length > 0)
setValue(
"clientSubsidiaryId",
clientSubsidiaryId !== undefined && clientSubsidiaryId !== null
? subsidiaryIds.includes(clientSubsidiaryId)
? clientSubsidiaryId
: null
: null,
);
// if (contacts.length > 0) setValue("clientContactId", contacts[0].id)
// else setValue("clientContactId", undefined)
},
);
}
}, [selectedCustomerId]);

useEffect(() => {
if (Boolean(clientSubsidiaryId)) {
// get subsidiary contact combo
const contacts = allSubsidiaries.find(subsidiary => subsidiary.id === clientSubsidiaryId)?.subsidiaryContacts!!
setSubsidiaryContacts(() => contacts)
setValue("clientContactId", selectedCustomerId === defaultValues?.clientId && Boolean(defaultValues?.clientSubsidiaryId) ? contacts.find(contact => contact.id === defaultValues.clientContactId)?.id ?? contacts[0].id : contacts[0].id)
setValue("isSubsidiaryContact", true)
const contacts = allSubsidiaries.find(
(subsidiary) => subsidiary.id === clientSubsidiaryId,
)!.subsidiaryContacts;
setSubsidiaryContacts(() => contacts);
setValue(
"clientContactId",
selectedCustomerId === defaultValues?.clientId &&
Boolean(defaultValues?.clientSubsidiaryId)
? contacts.find(
(contact) => contact.id === defaultValues.clientContactId,
)?.id ?? contacts[0].id
: contacts[0].id,
);
setValue("isSubsidiaryContact", true);
} else if (customerContacts?.length > 0) {
setSubsidiaryContacts(() => [])
setValue("clientContactId", selectedCustomerId === defaultValues?.clientId && !Boolean(defaultValues?.clientSubsidiaryId) ? customerContacts.find(contact => contact.id === defaultValues.clientContactId)?.id ?? customerContacts[0].id : customerContacts[0].id)
setValue("isSubsidiaryContact", false)
setSubsidiaryContacts(() => []);
setValue(
"clientContactId",
selectedCustomerId === defaultValues?.clientId &&
!Boolean(defaultValues?.clientSubsidiaryId)
? customerContacts.find(
(contact) => contact.id === defaultValues.clientContactId,
)?.id ?? customerContacts[0].id
: customerContacts[0].id,
);
setValue("isSubsidiaryContact", false);
}
}, [customerContacts, clientSubsidiaryId, selectedCustomerId]);

@@ -155,31 +184,37 @@ const ProjectClientDetails: React.FC<Props> = ({
}, [getValues, selectedTeamLeadId, setValue]);

// Automatically update the project & client details whene select a main project
const mainProjectId = watch("mainProjectId")
const mainProjectId = watch("mainProjectId");
useEffect(() => {
if (mainProjectId !== undefined && mainProjects !== undefined && !isEditMode) {
const mainProject = mainProjects.find(project => project.projectId === mainProjectId);
if (
mainProjectId !== undefined &&
mainProjects !== undefined &&
!isEditMode
) {
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("subContractFee", mainProject.subContractFee)
setValue("isClpProject", mainProject.isClpProject)
setValue("clientId", mainProject.clientId)
setValue("clientSubsidiaryId", mainProject.clientSubsidiaryId)
setValue("clientContactId", mainProject.clientContactId)
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("subContractFee", mainProject.subContractFee);
setValue("isClpProject", mainProject.isClpProject);
setValue("clientId", mainProject.clientId);
setValue("clientSubsidiaryId", mainProject.clientSubsidiaryId);
setValue("clientContactId", mainProject.clientContactId);
}
}
}, [getValues, mainProjectId, setValue, isEditMode])
}, [getValues, mainProjectId, setValue, isEditMode]);

// const buildingTypeIdNameMap = buildingTypes.reduce<{ [id: number]: string }>(
// (acc, building) => ({ ...acc, [building.id]: building.name }),
@@ -199,29 +234,36 @@ const ProjectClientDetails: React.FC<Props> = ({
{t("Project Details")}
</Typography>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
{
isSubProject && mainProjects !== undefined && <><Grid item xs={6}>
<ControlledAutoComplete
control={control}
options={[...mainProjects.map(mainProject => ({ id: mainProject.projectId, label: `${mainProject.projectCode} - ${mainProject.projectName}` }))]}
name="mainProjectId"
label={t("Main Project")}
noOptionsText={t("No Main Project")}
disabled={isEditMode}
/>
</Grid>
<Grid item sx={{ display: { xs: "none", sm: "block" } }} /></>
}
{isSubProject && mainProjects !== undefined && (
<>
<Grid item xs={6}>
<ControlledAutoComplete
control={control}
options={[
...mainProjects.map((mainProject) => ({
id: mainProject.projectId,
label: `${mainProject.projectCode} - ${mainProject.projectName}`,
})),
]}
name="mainProjectId"
label={t("Main Project")}
noOptionsText={t("No Main Project")}
disabled={isEditMode}
/>
</Grid>
<Grid item sx={{ display: { xs: "none", sm: "block" } }} />
</>
)}
<Grid item xs={6}>
<TextField
label={t("Project Code")}
fullWidth
disabled={isSubProject && mainProjects !== undefined}
{...register("projectCode",
{
required: !(isSubProject && mainProjects !== undefined) && "Project code required!",
}
)}
{...register("projectCode", {
required:
!(isSubProject && mainProjects !== undefined) &&
"Project code required!",
})}
error={Boolean(errors.projectCode)}
/>
</Grid>
@@ -247,7 +289,10 @@ const ProjectClientDetails: React.FC<Props> = ({
<Grid item xs={6}>
<ControlledAutoComplete
control={control}
options={teamLeads.map((staff) => ({ ...staff, label: `${staff.staffId} - ${staff.name} (${staff.team})` }))}
options={teamLeads.map((staff) => ({
...staff,
label: `${staff.staffId} - ${staff.name} (${staff.team})`,
}))}
name="projectLeadId"
label={t("Team Lead")}
noOptionsText={t("No Team Lead")}
@@ -338,7 +383,7 @@ const ProjectClientDetails: React.FC<Props> = ({
type="number"
inputProps={{ step: "0.01" }}
InputLabelProps={{
shrink: Boolean(watch("subContractFee"))
shrink: Boolean(watch("subContractFee")),
}}
{...register("subContractFee", { valueAsNumber: true })}
/>
@@ -375,24 +420,29 @@ const ProjectClientDetails: React.FC<Props> = ({
<Grid item xs={6}>
<ControlledAutoComplete
control={control}
options={allCustomers.map((customer) => ({ ...customer, label: `${customer.code} - ${customer.name}` }))}
options={allCustomers.map((customer) => ({
...customer,
label: `${customer.code} - ${customer.name}`,
}))}
name="clientId"
label={t("Client")}
noOptionsText={t("No Client")}
rules={{
required: "Please select a client"
required: "Please select a client",
}}
/>
</Grid>
<Grid item sx={{ display: { xs: "none", sm: "block" } }} />
<Grid item xs={6}>
<TextField
<ControlledAutoComplete
control={control}
options={customerTypes}
name="clientTypeId"
label={t("Client Type")}
InputProps={{
readOnly: true,
noOptionsText={t("No Client Type")}
rules={{
required: "Please select a client type",
}}
fullWidth
value={selectedCustomer?.customerType.name || ""}
/>
</Grid>
<Grid item sx={{ display: { xs: "none", sm: "block" } }} />
@@ -401,12 +451,18 @@ const ProjectClientDetails: React.FC<Props> = ({
<Grid item xs={6}>
<ControlledAutoComplete
control={control}
options={[{ label: t("No Subsidiary") }, ...customerSubsidiaryIds
.filter((subId) => subsidiaryMap[subId])
.map((subsidiaryId, index) => {
const subsidiary = subsidiaryMap[subsidiaryId]
return { id: subsidiary.id, label: `${subsidiary.code} - ${subsidiary.name}` }
})]}
options={[
{ label: t("No Subsidiary") },
...customerSubsidiaryIds
.filter((subId) => subsidiaryMap[subId])
.map((subsidiaryId, index) => {
const subsidiary = subsidiaryMap[subsidiaryId];
return {
id: subsidiary.id,
label: `${subsidiary.code} - ${subsidiary.name}`,
};
}),
]}
name="clientSubsidiaryId"
label={t("Client Subsidiary")}
noOptionsText={t("No Client Subsidiary")}
@@ -415,18 +471,25 @@ const ProjectClientDetails: React.FC<Props> = ({
<Grid item xs={6}>
<ControlledAutoComplete
control={control}
options={Boolean(watch("clientSubsidiaryId")) ? subsidiaryContacts : customerContacts}
options={
Boolean(watch("clientSubsidiaryId"))
? subsidiaryContacts
: customerContacts
}
name="clientContactId"
label={t("Client Lead")}
noOptionsText={t("No Client Lead")}
rules={{
validate: (value) => {
if (
(customerContacts.length > 0 && !customerContacts.find(
customerContacts.length > 0 &&
!customerContacts.find(
(contact) => contact.id === value,
)) && (subsidiaryContacts?.length > 0 && !subsidiaryContacts.find(
) &&
subsidiaryContacts?.length > 0 &&
!subsidiaryContacts.find(
(contact) => contact.id === value,
))
)
) {
return t("Please provide a valid contact");
} else return true;


Caricamento…
Annulla
Salva