| @@ -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 { fetchGrades } from "@/app/api/grades"; | ||||
| import { | import { | ||||
| fetchProjectBuildingTypes, | fetchProjectBuildingTypes, | ||||
| @@ -26,9 +26,9 @@ export const metadata: Metadata = { | |||||
| const Projects: React.FC = async () => { | const Projects: React.FC = async () => { | ||||
| const { t } = await getServerI18n("projects"); | 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(); | notFound(); | ||||
| } | } | ||||
| @@ -44,6 +44,7 @@ const Projects: React.FC = async () => { | |||||
| fetchProjectWorkNatures(); | fetchProjectWorkNatures(); | ||||
| fetchAllCustomers(); | fetchAllCustomers(); | ||||
| fetchAllSubsidiaries(); | fetchAllSubsidiaries(); | ||||
| fetchCustomerTypes(); | |||||
| fetchGrades(); | fetchGrades(); | ||||
| preloadTeamLeads(); | preloadTeamLeads(); | ||||
| preloadStaff(); | preloadStaff(); | ||||
| @@ -40,6 +40,7 @@ export interface CreateProjectInputs { | |||||
| clientSubsidiaryId?: number | null; | clientSubsidiaryId?: number | null; | ||||
| subsidiaryContactId?: number; | subsidiaryContactId?: number; | ||||
| isSubsidiaryContact?: boolean; | isSubsidiaryContact?: boolean; | ||||
| clientTypeId?: number; | |||||
| // Allocation | // Allocation | ||||
| totalManhour: number; | totalManhour: number; | ||||
| @@ -117,12 +118,12 @@ export const deleteProject = async (id: number) => { | |||||
| }; | }; | ||||
| export const importProjects = async (data: FormData) => { | 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; | return importProjects; | ||||
| @@ -41,7 +41,7 @@ import { | |||||
| import { StaffResult } from "@/app/api/staff"; | import { StaffResult } from "@/app/api/staff"; | ||||
| import { Typography } from "@mui/material"; | import { Typography } from "@mui/material"; | ||||
| import { Grade } from "@/app/api/grades"; | 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 { isEmpty } from "lodash"; | ||||
| import { | import { | ||||
| deleteDialog, | deleteDialog, | ||||
| @@ -70,6 +70,7 @@ export interface Props { | |||||
| buildingTypes: BuildingType[]; | buildingTypes: BuildingType[]; | ||||
| workNatures: WorkNature[]; | workNatures: WorkNature[]; | ||||
| allStaffs: StaffResult[]; | allStaffs: StaffResult[]; | ||||
| customerTypes: CustomerType[]; | |||||
| grades: Grade[]; | grades: Grade[]; | ||||
| abilities: string[]; | abilities: string[]; | ||||
| } | } | ||||
| @@ -81,16 +82,19 @@ const hasErrorsInTab = ( | |||||
| switch (tabIndex) { | switch (tabIndex) { | ||||
| case 0: | case 0: | ||||
| return ( | return ( | ||||
| errors.projectName || errors.projectDescription || errors.clientId || errors.projectCode | |||||
| errors.projectName || | |||||
| errors.projectDescription || | |||||
| errors.clientId || | |||||
| errors.projectCode | |||||
| ); | ); | ||||
| case 2: | case 2: | ||||
| return ( | return ( | ||||
| errors.totalManhour || errors.manhourPercentageByGrade || errors.taskGroups | |||||
| errors.totalManhour || | |||||
| errors.manhourPercentageByGrade || | |||||
| errors.taskGroups | |||||
| ); | ); | ||||
| case 3: | case 3: | ||||
| return ( | |||||
| errors.milestones | |||||
| ) | |||||
| return errors.milestones; | |||||
| default: | default: | ||||
| false; | false; | ||||
| } | } | ||||
| @@ -115,6 +119,7 @@ const CreateProject: React.FC<Props> = ({ | |||||
| buildingTypes, | buildingTypes, | ||||
| workNatures, | workNatures, | ||||
| allStaffs, | allStaffs, | ||||
| customerTypes, | |||||
| abilities, | abilities, | ||||
| }) => { | }) => { | ||||
| const [serverError, setServerError] = useState(""); | const [serverError, setServerError] = useState(""); | ||||
| @@ -151,53 +156,102 @@ const CreateProject: React.FC<Props> = ({ | |||||
| console.log(data); | console.log(data); | ||||
| // detect errors | // detect errors | ||||
| let hasErrors = false | |||||
| let hasErrors = false; | |||||
| // Tab - Staff Allocation and Resource | // Tab - Staff Allocation and Resource | ||||
| if (data.totalManhour === null || data.totalManhour <= 0) { | 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 | // 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 | // save project | ||||
| setServerError(""); | setServerError(""); | ||||
| @@ -227,18 +281,29 @@ const CreateProject: React.FC<Props> = ({ | |||||
| data.projectActualEnd = dayjs().format("YYYY-MM-DD"); | 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); | 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(() => { | successDialog(successTitle, t).then(() => { | ||||
| router.replace("/projects"); | router.replace("/projects"); | ||||
| }); | }); | ||||
| } else { | } else { | ||||
| errorDialog(response.message ?? errorTitle, t).then(() => { | 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; | return false; | ||||
| @@ -257,7 +322,7 @@ const CreateProject: React.FC<Props> = ({ | |||||
| const onSubmitError = useCallback<SubmitErrorHandler<CreateProjectInputs>>( | const onSubmitError = useCallback<SubmitErrorHandler<CreateProjectInputs>>( | ||||
| (errors) => { | (errors) => { | ||||
| console.log(errors) | |||||
| console.log(errors); | |||||
| // Set the tab so that the focus will go there | // Set the tab so that the focus will go there | ||||
| if ( | if ( | ||||
| errors.projectName || | errors.projectName || | ||||
| @@ -266,10 +331,14 @@ const CreateProject: React.FC<Props> = ({ | |||||
| errors.clientId | errors.clientId | ||||
| ) { | ) { | ||||
| setTabIndex(0); | 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) { | } else if (errors.milestones) { | ||||
| setTabIndex(3) | |||||
| setTabIndex(3); | |||||
| } | } | ||||
| }, | }, | ||||
| [], | [], | ||||
| @@ -282,18 +351,26 @@ const CreateProject: React.FC<Props> = ({ | |||||
| milestones: {}, | milestones: {}, | ||||
| totalManhour: 0, | totalManhour: 0, | ||||
| taskTemplateId: "All", | 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, | clientId: allCustomers !== undefined ? allCustomers[0].id : undefined, | ||||
| ...defaultInputs, | ...defaultInputs, | ||||
| // manhourPercentageByGrade should have a sensible default | // manhourPercentageByGrade should have a sensible default | ||||
| manhourPercentageByGrade: isEmpty(defaultInputs?.manhourPercentageByGrade) | manhourPercentageByGrade: isEmpty(defaultInputs?.manhourPercentageByGrade) | ||||
| ? grades.reduce((acc, grade) => { | ? grades.reduce((acc, grade) => { | ||||
| return { ...acc, [grade.id]: 100 / grades.length }; | |||||
| }, {}) | |||||
| return { ...acc, [grade.id]: 100 / grades.length }; | |||||
| }, {}) | |||||
| : defaultInputs?.manhourPercentageByGrade, | : defaultInputs?.manhourPercentageByGrade, | ||||
| }, | }, | ||||
| }); | }); | ||||
| @@ -311,7 +388,8 @@ const CreateProject: React.FC<Props> = ({ | |||||
| {isEditMode && !(formProps.getValues("projectDeleted") === true) && ( | {isEditMode && !(formProps.getValues("projectDeleted") === true) && ( | ||||
| <Stack direction="row" gap={1}> | <Stack direction="row" gap={1}> | ||||
| {/* {!formProps.getValues("projectActualStart") && ( */} | {/* {!formProps.getValues("projectActualStart") && ( */} | ||||
| {formProps.getValues("projectStatus")?.toLowerCase() === "pending to start" && ( | |||||
| {formProps.getValues("projectStatus")?.toLowerCase() === | |||||
| "pending to start" && ( | |||||
| <Button | <Button | ||||
| name="start" | name="start" | ||||
| type="submit" | type="submit" | ||||
| @@ -324,7 +402,8 @@ const CreateProject: React.FC<Props> = ({ | |||||
| )} | )} | ||||
| {/* {formProps.getValues("projectActualStart") && | {/* {formProps.getValues("projectActualStart") && | ||||
| !formProps.getValues("projectActualEnd") && ( */} | !formProps.getValues("projectActualEnd") && ( */} | ||||
| {formProps.getValues("projectStatus")?.toLowerCase() === "on-going" && ( | |||||
| {formProps.getValues("projectStatus")?.toLowerCase() === | |||||
| "on-going" && ( | |||||
| <Button | <Button | ||||
| name="complete" | name="complete" | ||||
| type="submit" | type="submit" | ||||
| @@ -338,9 +417,14 @@ const CreateProject: React.FC<Props> = ({ | |||||
| {!( | {!( | ||||
| // formProps.getValues("projectActualStart") && | // formProps.getValues("projectActualStart") && | ||||
| // formProps.getValues("projectActualEnd") | // 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 | <Button | ||||
| variant="outlined" | variant="outlined" | ||||
| startIcon={<Delete />} | startIcon={<Delete />} | ||||
| @@ -359,7 +443,13 @@ const CreateProject: React.FC<Props> = ({ | |||||
| > | > | ||||
| <Tab | <Tab | ||||
| label={t("Project and Client Details")} | 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={ | icon={ | ||||
| hasErrorsInTab(0, errors) ? ( | hasErrorsInTab(0, errors) ? ( | ||||
| <Error sx={{ marginInlineEnd: 1 }} color="error" /> | <Error sx={{ marginInlineEnd: 1 }} color="error" /> | ||||
| @@ -369,11 +459,22 @@ const CreateProject: React.FC<Props> = ({ | |||||
| /> | /> | ||||
| <Tab | <Tab | ||||
| label={t("Project Task Setup")} | 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 | <Tab | ||||
| label={t("Staff Allocation and Resource")} | 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={ | icon={ | ||||
| hasErrorsInTab(2, errors) ? ( | hasErrorsInTab(2, errors) ? ( | ||||
| <Error sx={{ marginInlineEnd: 1 }} color="error" /> | <Error sx={{ marginInlineEnd: 1 }} color="error" /> | ||||
| @@ -381,12 +482,15 @@ const CreateProject: React.FC<Props> = ({ | |||||
| } | } | ||||
| iconPosition="end" | iconPosition="end" | ||||
| /> | /> | ||||
| <Tab label={t("Milestone")} | |||||
| <Tab | |||||
| label={t("Milestone")} | |||||
| icon={ | icon={ | ||||
| hasErrorsInTab(3, errors) ? ( | hasErrorsInTab(3, errors) ? ( | ||||
| <Error sx={{ marginInlineEnd: 1 }} color="error" />) | |||||
| : undefined} | |||||
| iconPosition="end" /> | |||||
| <Error sx={{ marginInlineEnd: 1 }} color="error" /> | |||||
| ) : undefined | |||||
| } | |||||
| iconPosition="end" | |||||
| /> | |||||
| </Tabs> | </Tabs> | ||||
| { | { | ||||
| <ProjectClientDetails | <ProjectClientDetails | ||||
| @@ -401,6 +505,7 @@ const CreateProject: React.FC<Props> = ({ | |||||
| allCustomers={allCustomers} | allCustomers={allCustomers} | ||||
| allSubsidiaries={allSubsidiaries} | allSubsidiaries={allSubsidiaries} | ||||
| projectCategories={projectCategories} | projectCategories={projectCategories} | ||||
| customerTypes={customerTypes} | |||||
| teamLeads={teamLeads} | teamLeads={teamLeads} | ||||
| isActive={tabIndex === 0} | isActive={tabIndex === 0} | ||||
| isEditMode={isEditMode} | isEditMode={isEditMode} | ||||
| @@ -440,11 +545,14 @@ const CreateProject: React.FC<Props> = ({ | |||||
| startIcon={<Check />} | startIcon={<Check />} | ||||
| type="submit" | type="submit" | ||||
| disabled={ | 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")} | {isEditMode ? t("Save") : t("Confirm")} | ||||
| @@ -12,7 +12,11 @@ import { | |||||
| fetchProjectWorkNatures, | fetchProjectWorkNatures, | ||||
| } from "@/app/api/projects"; | } from "@/app/api/projects"; | ||||
| import { fetchStaff, fetchTeamLeads } from "@/app/api/staff"; | 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 { fetchGrades } from "@/app/api/grades"; | ||||
| import { getUserAbilities } from "@/app/utils/commonUtil"; | import { getUserAbilities } from "@/app/utils/commonUtil"; | ||||
| @@ -44,6 +48,7 @@ const CreateProjectWrapper: React.FC<Props> = async (props) => { | |||||
| workNatures, | workNatures, | ||||
| allStaffs, | allStaffs, | ||||
| grades, | grades, | ||||
| customerTypes, | |||||
| abilities, | abilities, | ||||
| ] = await Promise.all([ | ] = await Promise.all([ | ||||
| fetchAllTasks(), | fetchAllTasks(), | ||||
| @@ -60,6 +65,7 @@ const CreateProjectWrapper: React.FC<Props> = async (props) => { | |||||
| fetchProjectWorkNatures(), | fetchProjectWorkNatures(), | ||||
| fetchStaff(), | fetchStaff(), | ||||
| fetchGrades(), | fetchGrades(), | ||||
| fetchCustomerTypes(), | |||||
| getUserAbilities(), | getUserAbilities(), | ||||
| ]); | ]); | ||||
| @@ -90,6 +96,7 @@ const CreateProjectWrapper: React.FC<Props> = async (props) => { | |||||
| workNatures={workNatures} | workNatures={workNatures} | ||||
| allStaffs={allStaffs} | allStaffs={allStaffs} | ||||
| grades={grades} | grades={grades} | ||||
| customerTypes={customerTypes} | |||||
| mainProjects={mainProjects} | mainProjects={mainProjects} | ||||
| abilities={abilities} | abilities={abilities} | ||||
| /> | /> | ||||
| @@ -4,18 +4,12 @@ import Stack from "@mui/material/Stack"; | |||||
| import Box from "@mui/material/Box"; | import Box from "@mui/material/Box"; | ||||
| import Card from "@mui/material/Card"; | import Card from "@mui/material/Card"; | ||||
| import CardContent from "@mui/material/CardContent"; | import CardContent from "@mui/material/CardContent"; | ||||
| import FormControl from "@mui/material/FormControl"; | |||||
| import Grid from "@mui/material/Grid"; | 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 TextField from "@mui/material/TextField"; | ||||
| import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||
| import { useTranslation } from "react-i18next"; | 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 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 { CreateProjectInputs } from "@/app/api/projects/actions"; | ||||
| import { | import { | ||||
| BuildingType, | BuildingType, | ||||
| @@ -28,11 +22,15 @@ import { | |||||
| WorkNature, | WorkNature, | ||||
| } from "@/app/api/projects"; | } from "@/app/api/projects"; | ||||
| import { StaffResult } from "@/app/api/staff"; | 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 Link from "next/link"; | ||||
| import React, { useEffect, useMemo, useState } from "react"; | import React, { useEffect, useMemo, useState } from "react"; | ||||
| import { fetchCustomer } from "@/app/api/customer/actions"; | import { fetchCustomer } from "@/app/api/customer/actions"; | ||||
| import { Autocomplete, Checkbox, ListItemText } from "@mui/material"; | |||||
| import uniq from "lodash/uniq"; | import uniq from "lodash/uniq"; | ||||
| import ControlledAutoComplete from "../ControlledAutoComplete/ControlledAutoComplete"; | import ControlledAutoComplete from "../ControlledAutoComplete/ControlledAutoComplete"; | ||||
| @@ -51,6 +49,7 @@ interface Props { | |||||
| locationTypes: LocationType[]; | locationTypes: LocationType[]; | ||||
| buildingTypes: BuildingType[]; | buildingTypes: BuildingType[]; | ||||
| workNatures: WorkNature[]; | workNatures: WorkNature[]; | ||||
| customerTypes: CustomerType[]; | |||||
| } | } | ||||
| const ProjectClientDetails: React.FC<Props> = ({ | const ProjectClientDetails: React.FC<Props> = ({ | ||||
| @@ -67,6 +66,7 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| fundingTypes, | fundingTypes, | ||||
| locationTypes, | locationTypes, | ||||
| buildingTypes, | buildingTypes, | ||||
| customerTypes, | |||||
| workNatures, | workNatures, | ||||
| }) => { | }) => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| @@ -89,10 +89,6 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| ); | ); | ||||
| const selectedCustomerId = watch("clientId"); | const selectedCustomerId = watch("clientId"); | ||||
| const selectedCustomer = useMemo( | |||||
| () => allCustomers.find((c) => c.id === selectedCustomerId), | |||||
| [allCustomers, selectedCustomerId], | |||||
| ); | |||||
| const [customerContacts, setCustomerContacts] = useState<Contact[]>([]); | const [customerContacts, setCustomerContacts] = useState<Contact[]>([]); | ||||
| const [subsidiaryContacts, setSubsidiaryContacts] = useState<Contact[]>([]); | const [subsidiaryContacts, setSubsidiaryContacts] = useState<Contact[]>([]); | ||||
| @@ -103,44 +99,77 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| const selectedCustomerContactId = watch("clientContactId"); | const selectedCustomerContactId = watch("clientContactId"); | ||||
| const selectedCustomerContact = useMemo( | const selectedCustomerContact = useMemo( | ||||
| () => | () => | ||||
| subsidiaryContacts.length > 0 ? | |||||
| subsidiaryContacts.find((contact) => contact.id === selectedCustomerContactId) | |||||
| subsidiaryContacts.length > 0 | |||||
| ? subsidiaryContacts.find( | |||||
| (contact) => contact.id === selectedCustomerContactId, | |||||
| ) | |||||
| : customerContacts.find( | : customerContacts.find( | ||||
| (contact) => contact.id === selectedCustomerContactId, | |||||
| ), | |||||
| (contact) => contact.id === selectedCustomerContactId, | |||||
| ), | |||||
| [subsidiaryContacts, customerContacts, selectedCustomerContactId], | [subsidiaryContacts, customerContacts, selectedCustomerContactId], | ||||
| ); | ); | ||||
| // get customer (client) contact combo | // get customer (client) contact combo | ||||
| const clientSubsidiaryId = watch("clientSubsidiaryId") | |||||
| const [firstCustomerLoaded, setFirstCustomerLoaded] = useState(false) | |||||
| const clientSubsidiaryId = watch("clientSubsidiaryId"); | |||||
| const [firstCustomerLoaded, setFirstCustomerLoaded] = useState(false); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (selectedCustomerId !== undefined) { | 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]); | }, [selectedCustomerId]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (Boolean(clientSubsidiaryId)) { | if (Boolean(clientSubsidiaryId)) { | ||||
| // get subsidiary contact combo | // 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) { | } 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]); | }, [customerContacts, clientSubsidiaryId, selectedCustomerId]); | ||||
| @@ -155,31 +184,37 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| }, [getValues, selectedTeamLeadId, setValue]); | }, [getValues, selectedTeamLeadId, setValue]); | ||||
| // Automatically update the project & client details whene select a main project | // Automatically update the project & client details whene select a main project | ||||
| const mainProjectId = watch("mainProjectId") | |||||
| const mainProjectId = watch("mainProjectId"); | |||||
| useEffect(() => { | 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) { | 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 }>( | // const buildingTypeIdNameMap = buildingTypes.reduce<{ [id: number]: string }>( | ||||
| // (acc, building) => ({ ...acc, [building.id]: building.name }), | // (acc, building) => ({ ...acc, [building.id]: building.name }), | ||||
| @@ -199,29 +234,36 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| {t("Project Details")} | {t("Project Details")} | ||||
| </Typography> | </Typography> | ||||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | <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}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| label={t("Project Code")} | label={t("Project Code")} | ||||
| fullWidth | fullWidth | ||||
| disabled={isSubProject && mainProjects !== undefined} | 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)} | error={Boolean(errors.projectCode)} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| @@ -247,7 +289,10 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <ControlledAutoComplete | <ControlledAutoComplete | ||||
| control={control} | 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" | name="projectLeadId" | ||||
| label={t("Team Lead")} | label={t("Team Lead")} | ||||
| noOptionsText={t("No Team Lead")} | noOptionsText={t("No Team Lead")} | ||||
| @@ -338,7 +383,7 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| type="number" | type="number" | ||||
| inputProps={{ step: "0.01" }} | inputProps={{ step: "0.01" }} | ||||
| InputLabelProps={{ | InputLabelProps={{ | ||||
| shrink: Boolean(watch("subContractFee")) | |||||
| shrink: Boolean(watch("subContractFee")), | |||||
| }} | }} | ||||
| {...register("subContractFee", { valueAsNumber: true })} | {...register("subContractFee", { valueAsNumber: true })} | ||||
| /> | /> | ||||
| @@ -375,24 +420,29 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <ControlledAutoComplete | <ControlledAutoComplete | ||||
| control={control} | control={control} | ||||
| options={allCustomers.map((customer) => ({ ...customer, label: `${customer.code} - ${customer.name}` }))} | |||||
| options={allCustomers.map((customer) => ({ | |||||
| ...customer, | |||||
| label: `${customer.code} - ${customer.name}`, | |||||
| }))} | |||||
| name="clientId" | name="clientId" | ||||
| label={t("Client")} | label={t("Client")} | ||||
| noOptionsText={t("No Client")} | noOptionsText={t("No Client")} | ||||
| rules={{ | rules={{ | ||||
| required: "Please select a client" | |||||
| required: "Please select a client", | |||||
| }} | }} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item sx={{ display: { xs: "none", sm: "block" } }} /> | <Grid item sx={{ display: { xs: "none", sm: "block" } }} /> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | |||||
| <ControlledAutoComplete | |||||
| control={control} | |||||
| options={customerTypes} | |||||
| name="clientTypeId" | |||||
| label={t("Client Type")} | 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> | ||||
| <Grid item sx={{ display: { xs: "none", sm: "block" } }} /> | <Grid item sx={{ display: { xs: "none", sm: "block" } }} /> | ||||
| @@ -401,12 +451,18 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <ControlledAutoComplete | <ControlledAutoComplete | ||||
| control={control} | 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" | name="clientSubsidiaryId" | ||||
| label={t("Client Subsidiary")} | label={t("Client Subsidiary")} | ||||
| noOptionsText={t("No Client Subsidiary")} | noOptionsText={t("No Client Subsidiary")} | ||||
| @@ -415,18 +471,25 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <ControlledAutoComplete | <ControlledAutoComplete | ||||
| control={control} | control={control} | ||||
| options={Boolean(watch("clientSubsidiaryId")) ? subsidiaryContacts : customerContacts} | |||||
| options={ | |||||
| Boolean(watch("clientSubsidiaryId")) | |||||
| ? subsidiaryContacts | |||||
| : customerContacts | |||||
| } | |||||
| name="clientContactId" | name="clientContactId" | ||||
| label={t("Client Lead")} | label={t("Client Lead")} | ||||
| noOptionsText={t("No Client Lead")} | noOptionsText={t("No Client Lead")} | ||||
| rules={{ | rules={{ | ||||
| validate: (value) => { | validate: (value) => { | ||||
| if ( | if ( | ||||
| (customerContacts.length > 0 && !customerContacts.find( | |||||
| customerContacts.length > 0 && | |||||
| !customerContacts.find( | |||||
| (contact) => contact.id === value, | (contact) => contact.id === value, | ||||
| )) && (subsidiaryContacts?.length > 0 && !subsidiaryContacts.find( | |||||
| ) && | |||||
| subsidiaryContacts?.length > 0 && | |||||
| !subsidiaryContacts.find( | |||||
| (contact) => contact.id === value, | (contact) => contact.id === value, | ||||
| )) | |||||
| ) | |||||
| ) { | ) { | ||||
| return t("Please provide a valid contact"); | return t("Please provide a valid contact"); | ||||
| } else return true; | } else return true; | ||||