| @@ -6,6 +6,7 @@ import Box from "@mui/material/Box"; | |||||
| import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig"; | import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig"; | ||||
| import Stack from "@mui/material/Stack"; | import Stack from "@mui/material/Stack"; | ||||
| import Breadcrumb from "@/components/Breadcrumb"; | import Breadcrumb from "@/components/Breadcrumb"; | ||||
| import { I18nProvider } from "@/i18n"; | |||||
| export default async function MainLayout({ | export default async function MainLayout({ | ||||
| children, | children, | ||||
| @@ -31,10 +32,12 @@ export default async function MainLayout({ | |||||
| padding: { xs: "1rem", sm: "1.5rem", lg: "3rem" }, | padding: { xs: "1rem", sm: "1.5rem", lg: "3rem" }, | ||||
| }} | }} | ||||
| > | > | ||||
| <Stack spacing={2}> | |||||
| <Breadcrumb /> | |||||
| {children} | |||||
| </Stack> | |||||
| <I18nProvider namespaces={["common"]}> | |||||
| <Stack spacing={2}> | |||||
| <Breadcrumb /> | |||||
| {children} | |||||
| </Stack> | |||||
| </I18nProvider> | |||||
| </Box> | </Box> | ||||
| </> | </> | ||||
| ); | ); | ||||
| @@ -11,6 +11,8 @@ export interface Customer { | |||||
| address: string | null; | address: string | null; | ||||
| district: string | null; | district: string | null; | ||||
| customerType: CustomerType; | customerType: CustomerType; | ||||
| contacts: Contact[]; | |||||
| } | } | ||||
| export interface SaveCustomerResponse { | export interface SaveCustomerResponse { | ||||
| @@ -40,6 +42,7 @@ export interface Subsidiary { | |||||
| district: string | null; | district: string | null; | ||||
| email: string | null; | email: string | null; | ||||
| subsidiaryType: SubsidiaryType; | subsidiaryType: SubsidiaryType; | ||||
| subsidiaryContacts: Contact[]; | |||||
| } | } | ||||
| export interface SubsidiaryTable { | export interface SubsidiaryTable { | ||||
| @@ -20,6 +20,8 @@ export interface CreateProjectInputs { | |||||
| projectLeadId: number; | projectLeadId: number; | ||||
| projectActualStart: string; | projectActualStart: string; | ||||
| projectActualEnd: string; | projectActualEnd: string; | ||||
| projectStatus: string; | |||||
| isClpProject: boolean; | |||||
| // Project info | // Project info | ||||
| serviceTypeId: number; | serviceTypeId: number; | ||||
| @@ -28,11 +30,14 @@ export interface CreateProjectInputs { | |||||
| locationId: number; | locationId: number; | ||||
| buildingTypeIds: number[]; | buildingTypeIds: number[]; | ||||
| workNatureIds: number[]; | workNatureIds: number[]; | ||||
| taskTemplateId?: number | "All"; | |||||
| // Client details | // Client details | ||||
| clientId: Customer["id"]; | clientId: Customer["id"]; | ||||
| clientContactId: number; | |||||
| clientContactId?: number; | |||||
| clientSubsidiaryId?: number; | clientSubsidiaryId?: number; | ||||
| subsidiaryContactId: number; | |||||
| isSubsidiaryContact?: boolean; | |||||
| // Allocation | // Allocation | ||||
| totalManhour: number; | totalManhour: number; | ||||
| @@ -12,6 +12,7 @@ export interface ProjectResult { | |||||
| category: string; | category: string; | ||||
| team: string; | team: string; | ||||
| client: string; | client: string; | ||||
| status: string; | |||||
| } | } | ||||
| export interface ProjectCategory { | export interface ProjectCategory { | ||||
| @@ -10,7 +10,9 @@ export interface Customer { | |||||
| brNo: string | null; | brNo: string | null; | ||||
| address: string | null; | address: string | null; | ||||
| district: string | null; | district: string | null; | ||||
| customerType: CustomerType | |||||
| customerType: CustomerType; | |||||
| contacts: Contact[]; | |||||
| } | } | ||||
| export interface CustomerTable { | export interface CustomerTable { | ||||
| @@ -40,7 +42,9 @@ export interface Subsidiary { | |||||
| brNo: string | null; | brNo: string | null; | ||||
| address: string | null; | address: string | null; | ||||
| district: string | null; | district: string | null; | ||||
| subsidiaryType: SubsidiaryType | |||||
| subsidiaryType: SubsidiaryType; | |||||
| contacts: Contact[]; | |||||
| } | } | ||||
| export interface SaveSubsidiaryResponse { | export interface SaveSubsidiaryResponse { | ||||
| @@ -76,7 +76,7 @@ const hasErrorsInTab = ( | |||||
| switch (tabIndex) { | switch (tabIndex) { | ||||
| case 0: | case 0: | ||||
| return ( | return ( | ||||
| errors.projectName || errors.projectCode || errors.projectDescription | |||||
| errors.projectName || errors.projectDescription || errors.clientId | |||||
| ); | ); | ||||
| case 2: | case 2: | ||||
| return ( | return ( | ||||
| @@ -114,6 +114,8 @@ const CreateProject: React.FC<Props> = ({ | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| console.log(defaultInputs) | |||||
| const handleCancel = () => { | const handleCancel = () => { | ||||
| router.replace("/projects"); | router.replace("/projects"); | ||||
| }; | }; | ||||
| @@ -219,6 +221,7 @@ 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; | |||||
| const response = await saveProject(data); | const response = await saveProject(data); | ||||
| if (response.id > 0) { | if (response.id > 0) { | ||||
| @@ -248,7 +251,8 @@ const CreateProject: React.FC<Props> = ({ | |||||
| if ( | if ( | ||||
| errors.projectName || | errors.projectName || | ||||
| errors.projectDescription || | errors.projectDescription || | ||||
| errors.projectCode | |||||
| // errors.projectCode || | |||||
| errors.clientId | |||||
| ) { | ) { | ||||
| setTabIndex(0); | setTabIndex(0); | ||||
| } else if (errors.totalManhour || errors.manhourPercentageByGrade || errors.taskGroups) { | } else if (errors.totalManhour || errors.manhourPercentageByGrade || errors.taskGroups) { | ||||
| @@ -266,6 +270,7 @@ const CreateProject: React.FC<Props> = ({ | |||||
| allocatedStaffIds: [], | allocatedStaffIds: [], | ||||
| milestones: {}, | milestones: {}, | ||||
| totalManhour: 0, | totalManhour: 0, | ||||
| taskTemplateId: "All", | |||||
| ...defaultInputs, | ...defaultInputs, | ||||
| // manhourPercentageByGrade should have a sensible default | // manhourPercentageByGrade should have a sensible default | ||||
| @@ -289,7 +294,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") === "Pending to Start" && ( | |||||
| <Button | <Button | ||||
| name="start" | name="start" | ||||
| type="submit" | type="submit" | ||||
| @@ -300,8 +306,9 @@ const CreateProject: React.FC<Props> = ({ | |||||
| {t("Start Project")} | {t("Start Project")} | ||||
| </Button> | </Button> | ||||
| )} | )} | ||||
| {formProps.getValues("projectActualStart") && | |||||
| !formProps.getValues("projectActualEnd") && ( | |||||
| {/* {formProps.getValues("projectActualStart") && | |||||
| !formProps.getValues("projectActualEnd") && ( */} | |||||
| {formProps.getValues("projectStatus") === "On-going" && ( | |||||
| <Button | <Button | ||||
| name="complete" | name="complete" | ||||
| type="submit" | type="submit" | ||||
| @@ -313,8 +320,10 @@ const CreateProject: React.FC<Props> = ({ | |||||
| </Button> | </Button> | ||||
| )} | )} | ||||
| {!( | {!( | ||||
| formProps.getValues("projectActualStart") && | |||||
| formProps.getValues("projectActualEnd") | |||||
| // formProps.getValues("projectActualStart") && | |||||
| // formProps.getValues("projectActualEnd") | |||||
| formProps.getValues("projectStatus") === "Completed" || | |||||
| formProps.getValues("projectStatus") === "Deleted" | |||||
| ) && ( | ) && ( | ||||
| <Button | <Button | ||||
| variant="outlined" | variant="outlined" | ||||
| @@ -412,7 +421,7 @@ const CreateProject: React.FC<Props> = ({ | |||||
| startIcon={<Check />} | startIcon={<Check />} | ||||
| type="submit" | type="submit" | ||||
| disabled={ | disabled={ | ||||
| formProps.getValues("projectDeleted") === true || | |||||
| formProps.getValues("projectDeleted") === true || formProps.getValues("projectStatus") === "Deleted" || | |||||
| (!!formProps.getValues("projectActualStart") && | (!!formProps.getValues("projectActualStart") && | ||||
| !!formProps.getValues("projectActualEnd")) | !!formProps.getValues("projectActualEnd")) | ||||
| } | } | ||||
| @@ -85,6 +85,7 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| ); | ); | ||||
| const [customerContacts, setCustomerContacts] = useState<Contact[]>([]); | const [customerContacts, setCustomerContacts] = useState<Contact[]>([]); | ||||
| const [subsidiaryContacts, setSubsidiaryContacts] = useState<Contact[]>([]); | |||||
| const [customerSubsidiaryIds, setCustomerSubsidiaryIds] = useState<number[]>( | const [customerSubsidiaryIds, setCustomerSubsidiaryIds] = useState<number[]>( | ||||
| [], | [], | ||||
| ); | ); | ||||
| @@ -92,21 +93,44 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| const selectedCustomerContactId = watch("clientContactId"); | const selectedCustomerContactId = watch("clientContactId"); | ||||
| const selectedCustomerContact = useMemo( | const selectedCustomerContact = useMemo( | ||||
| () => | () => | ||||
| customerContacts.find( | |||||
| (contact) => contact.id === selectedCustomerContactId, | |||||
| ), | |||||
| [customerContacts, selectedCustomerContactId], | |||||
| subsidiaryContacts.length > 0 ? | |||||
| subsidiaryContacts.find((contact) => contact.id === selectedCustomerContactId) | |||||
| : customerContacts.find( | |||||
| (contact) => contact.id === selectedCustomerContactId, | |||||
| ), | |||||
| [subsidiaryContacts, customerContacts, selectedCustomerContactId], | |||||
| ); | ); | ||||
| // get customer (client) contact combo | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (selectedCustomerId !== undefined) { | if (selectedCustomerId !== undefined) { | ||||
| fetchCustomer(selectedCustomerId).then(({ contacts, subsidiaryIds }) => { | fetchCustomer(selectedCustomerId).then(({ contacts, subsidiaryIds }) => { | ||||
| setCustomerContacts(contacts); | setCustomerContacts(contacts); | ||||
| setCustomerSubsidiaryIds(subsidiaryIds); | setCustomerSubsidiaryIds(subsidiaryIds); | ||||
| if (subsidiaryIds.length > 0) setValue("clientSubsidiaryId", subsidiaryIds[0]) | |||||
| else setValue("clientSubsidiaryId", undefined) | |||||
| // if (contacts.length > 0) setValue("clientContactId", contacts[0].id) | |||||
| // else setValue("clientContactId", undefined) | |||||
| }); | }); | ||||
| } | } | ||||
| }, [selectedCustomerId]); | }, [selectedCustomerId]); | ||||
| const clientSubsidiaryId = watch("clientSubsidiaryId") | |||||
| useEffect(() => { | |||||
| if (Boolean(clientSubsidiaryId)) { | |||||
| // get subsidiary contact combo | |||||
| const contacts = allSubsidiaries.find(subsidiary => subsidiary.id === clientSubsidiaryId)?.subsidiaryContacts!! | |||||
| setSubsidiaryContacts(contacts) | |||||
| setValue("clientContactId", contacts[0].id) | |||||
| setValue("isSubsidiaryContact", true) | |||||
| } else if (customerContacts?.length > 0) { | |||||
| setSubsidiaryContacts([]) | |||||
| setValue("clientContactId", customerContacts[0].id) | |||||
| setValue("isSubsidiaryContact", false) | |||||
| } | |||||
| }, [customerContacts, clientSubsidiaryId]); | |||||
| // Automatically add the team lead to the allocated staff list | // Automatically add the team lead to the allocated staff list | ||||
| const selectedTeamLeadId = watch("projectLeadId"); | const selectedTeamLeadId = watch("projectLeadId"); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -139,10 +163,13 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| <TextField | <TextField | ||||
| label={t("Project Code")} | label={t("Project Code")} | ||||
| fullWidth | fullWidth | ||||
| {...register("projectCode", { | |||||
| required: "Project code required!", | |||||
| })} | |||||
| error={Boolean(errors.projectCode)} | |||||
| disabled | |||||
| {...register("projectCode", | |||||
| // { | |||||
| // required: "Project code required!", | |||||
| // } | |||||
| )} | |||||
| // error={Boolean(errors.projectCode)} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| @@ -354,6 +381,16 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| {...register("expectedProjectFee", { valueAsNumber: true })} | {...register("expectedProjectFee", { valueAsNumber: true })} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | |||||
| <Checkbox | |||||
| {...register("isClpProject")} | |||||
| defaultChecked={watch("isClpProject")} | |||||
| /> | |||||
| <Typography variant="overline" display="inline"> | |||||
| {t("CLP Project")} | |||||
| </Typography> | |||||
| </Grid> | |||||
| </Grid> | </Grid> | ||||
| </Box> | </Box> | ||||
| @@ -373,12 +410,15 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| </Stack> | </Stack> | ||||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <FormControl fullWidth> | |||||
| <FormControl fullWidth error={Boolean(errors.clientId)}> | |||||
| <InputLabel>{t("Client")}</InputLabel> | <InputLabel>{t("Client")}</InputLabel> | ||||
| <Controller | <Controller | ||||
| defaultValue={allCustomers[0].id} | |||||
| defaultValue={allCustomers[0]?.id} | |||||
| control={control} | control={control} | ||||
| name="clientId" | name="clientId" | ||||
| rules={{ | |||||
| required: "Please select a client", | |||||
| }} | |||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <Select label={t("Client")} {...field}> | <Select label={t("Client")} {...field}> | ||||
| {allCustomers.map((customer, index) => ( | {allCustomers.map((customer, index) => ( | ||||
| @@ -408,6 +448,50 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| <Grid item sx={{ display: { xs: "none", sm: "block" } }} /> | <Grid item sx={{ display: { xs: "none", sm: "block" } }} /> | ||||
| {customerContacts.length > 0 && ( | {customerContacts.length > 0 && ( | ||||
| <> | <> | ||||
| {customerSubsidiaryIds.length > 0 && ( | |||||
| <Grid item xs={6}> | |||||
| <FormControl | |||||
| fullWidth | |||||
| error={Boolean(errors.clientSubsidiaryId)} | |||||
| > | |||||
| <InputLabel>{t("Client Subsidiary")}</InputLabel> | |||||
| <Controller | |||||
| // rules={{ | |||||
| // validate: (value) => { | |||||
| // if ( | |||||
| // !customerSubsidiaryIds.find( | |||||
| // (subsidiaryId) => subsidiaryId === value, | |||||
| // ) | |||||
| // ) { | |||||
| // return t("Please choose a valid subsidiary"); | |||||
| // } else return true; | |||||
| // }, | |||||
| // }} | |||||
| defaultValue={customerSubsidiaryIds[0]} | |||||
| control={control} | |||||
| name="clientSubsidiaryId" | |||||
| render={({ field }) => ( | |||||
| <Select label={t("Client Subsidiary")} {...field}> | |||||
| {customerSubsidiaryIds | |||||
| .filter((subId) => subsidiaryMap[subId]) | |||||
| .map((subsidiaryId, index) => { | |||||
| const subsidiary = subsidiaryMap[subsidiaryId]; | |||||
| return ( | |||||
| <MenuItem | |||||
| key={`${subsidiaryId}-${index}`} | |||||
| value={subsidiaryId} | |||||
| > | |||||
| {`${subsidiary.code} - ${subsidiary.name}`} | |||||
| </MenuItem> | |||||
| ); | |||||
| })} | |||||
| </Select> | |||||
| )} | |||||
| /> | |||||
| </FormControl> | |||||
| </Grid> | |||||
| )} | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <FormControl | <FormControl | ||||
| fullWidth | fullWidth | ||||
| @@ -418,33 +502,44 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| rules={{ | rules={{ | ||||
| validate: (value) => { | validate: (value) => { | ||||
| if ( | if ( | ||||
| !customerContacts.find( | |||||
| (customerContacts.length > 0 && !customerContacts.find( | |||||
| (contact) => contact.id === value, | |||||
| )) && (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; | ||||
| }, | }, | ||||
| }} | }} | ||||
| defaultValue={customerContacts[0].id} | |||||
| defaultValue={subsidiaryContacts?.length > 0 ? subsidiaryContacts[0].id : customerContacts[0].id} | |||||
| control={control} | control={control} | ||||
| name="clientContactId" | name="clientContactId" | ||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <Select label={t("Client Lead")} {...field}> | <Select label={t("Client Lead")} {...field}> | ||||
| {customerContacts.map((contact, index) => ( | |||||
| <MenuItem | |||||
| key={`${contact.id}-${index}`} | |||||
| value={contact.id} | |||||
| > | |||||
| {contact.name} | |||||
| </MenuItem> | |||||
| ))} | |||||
| {subsidiaryContacts?.length > 0 ? | |||||
| subsidiaryContacts.map((contact, index) => ( | |||||
| <MenuItem | |||||
| key={`${contact.id}-${index}`} | |||||
| value={contact.id} | |||||
| > | |||||
| {contact.name} | |||||
| </MenuItem> | |||||
| )) | |||||
| : customerContacts.map((contact, index) => ( | |||||
| <MenuItem | |||||
| key={`${contact.id}-${index}`} | |||||
| value={contact.id} | |||||
| > | |||||
| {contact.name} | |||||
| </MenuItem> | |||||
| ))} | |||||
| </Select> | </Select> | ||||
| )} | )} | ||||
| /> | /> | ||||
| </FormControl> | </FormControl> | ||||
| </Grid> | </Grid> | ||||
| <Grid item sx={{ display: { xs: "none", sm: "block" } }} /> | |||||
| <Grid container sx={{ display: { xs: "none", sm: "block" } }} /> | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <TextField | <TextField | ||||
| label={t("Client Lead Phone Number")} | label={t("Client Lead Phone Number")} | ||||
| @@ -467,50 +562,6 @@ const ProjectClientDetails: React.FC<Props> = ({ | |||||
| </Grid> | </Grid> | ||||
| </> | </> | ||||
| )} | )} | ||||
| {customerSubsidiaryIds.length > 0 && ( | |||||
| <Grid item xs={6}> | |||||
| <FormControl | |||||
| fullWidth | |||||
| error={Boolean(errors.clientSubsidiaryId)} | |||||
| > | |||||
| <InputLabel>{t("Client Subsidiary")}</InputLabel> | |||||
| <Controller | |||||
| // rules={{ | |||||
| // validate: (value) => { | |||||
| // if ( | |||||
| // !customerSubsidiaryIds.find( | |||||
| // (subsidiaryId) => subsidiaryId === value, | |||||
| // ) | |||||
| // ) { | |||||
| // return t("Please choose a valid subsidiary"); | |||||
| // } else return true; | |||||
| // }, | |||||
| // }} | |||||
| defaultValue={customerSubsidiaryIds[0]} | |||||
| control={control} | |||||
| name="clientSubsidiaryId" | |||||
| render={({ field }) => ( | |||||
| <Select label={t("Client Lead")} {...field}> | |||||
| {customerSubsidiaryIds | |||||
| .filter((subId) => subsidiaryMap[subId]) | |||||
| .map((subsidiaryId, index) => { | |||||
| const subsidiary = subsidiaryMap[subsidiaryId]; | |||||
| return ( | |||||
| <MenuItem | |||||
| key={`${subsidiaryId}-${index}`} | |||||
| value={subsidiaryId} | |||||
| > | |||||
| {`${subsidiary.code} - ${subsidiary.name}`} | |||||
| </MenuItem> | |||||
| ); | |||||
| })} | |||||
| </Select> | |||||
| )} | |||||
| /> | |||||
| </FormControl> | |||||
| </Grid> | |||||
| )} | |||||
| </Grid> | </Grid> | ||||
| </Box> | </Box> | ||||
| <CardActions sx={{ justifyContent: "flex-end" }}> | <CardActions sx={{ justifyContent: "flex-end" }}> | ||||
| @@ -33,7 +33,7 @@ const TaskSetup: React.FC<Props> = ({ | |||||
| isActive, | isActive, | ||||
| }) => { | }) => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const { setValue, watch, clearErrors, setError } = useFormContext<CreateProjectInputs>(); | |||||
| const { setValue, watch, clearErrors, setError, formState: { defaultValues } } = useFormContext<CreateProjectInputs>(); | |||||
| const currentTaskGroups = watch("taskGroups"); | const currentTaskGroups = watch("taskGroups"); | ||||
| const currentTaskIds = Object.values(currentTaskGroups).reduce<Task["id"][]>( | const currentTaskIds = Object.values(currentTaskGroups).reduce<Task["id"][]>( | ||||
| (acc, group) => { | (acc, group) => { | ||||
| @@ -48,7 +48,7 @@ const TaskSetup: React.FC<Props> = ({ | |||||
| const [selectedTaskTemplateId, setSelectedTaskTemplateId] = useState< | const [selectedTaskTemplateId, setSelectedTaskTemplateId] = useState< | ||||
| "All" | number | "All" | number | ||||
| >("All"); | |||||
| >(watch("taskTemplateId") ?? "All"); | |||||
| const onSelectTaskTemplate = useCallback( | const onSelectTaskTemplate = useCallback( | ||||
| (e: SelectChangeEvent<number | "All">) => { | (e: SelectChangeEvent<number | "All">) => { | ||||
| if (e.target.value === "All" || isNumber(e.target.value)) { | if (e.target.value === "All" || isNumber(e.target.value)) { | ||||
| @@ -64,7 +64,8 @@ const TaskSetup: React.FC<Props> = ({ | |||||
| (template) => template.id === selectedTaskTemplateId, | (template) => template.id === selectedTaskTemplateId, | ||||
| ) | ) | ||||
| if (selectedTaskTemplateId !== "All") { | |||||
| if (selectedTaskTemplateId !== "All" && selectedTaskTemplateId !== watch("taskTemplateId")) { | |||||
| // update the "manhour allocation by grade" by task template | // update the "manhour allocation by grade" by task template | ||||
| const updatedManhourPercentageByGrade: ManhourAllocation = watch("manhourPercentageByGrade") | const updatedManhourPercentageByGrade: ManhourAllocation = watch("manhourPercentageByGrade") | ||||
| selectedTaskTemplate?.gradeAllocations.forEach((gradeAllocation) => { | selectedTaskTemplate?.gradeAllocations.forEach((gradeAllocation) => { | ||||
| @@ -73,28 +74,30 @@ const TaskSetup: React.FC<Props> = ({ | |||||
| setValue("manhourPercentageByGrade", updatedManhourPercentageByGrade) | setValue("manhourPercentageByGrade", updatedManhourPercentageByGrade) | ||||
| if (Object.values(updatedManhourPercentageByGrade).reduce((acc, value) => acc + value, 0) === 100) clearErrors("manhourPercentageByGrade") | if (Object.values(updatedManhourPercentageByGrade).reduce((acc, value) => acc + value, 0) === 100) clearErrors("manhourPercentageByGrade") | ||||
| else setError("manhourPercentageByGrade", {message: "manhourPercentageByGrade value is not valid", type: "invalid"}) | |||||
| else setError("manhourPercentageByGrade", { message: "manhourPercentageByGrade value is not valid", type: "invalid" }) | |||||
| // update the "manhour allocation by grade by stage" by task template | // update the "manhour allocation by grade by stage" by task template | ||||
| const updatedTaskGroups = watch("taskGroups") | const updatedTaskGroups = watch("taskGroups") | ||||
| const taskGroupsKeys = Object.keys(updatedTaskGroups) | const taskGroupsKeys = Object.keys(updatedTaskGroups) | ||||
| selectedTaskTemplate?.groupAllocations.forEach((groupAllocation) => { | selectedTaskTemplate?.groupAllocations.forEach((groupAllocation) => { | ||||
| const taskGroupId = groupAllocation.taskGroup.id | const taskGroupId = groupAllocation.taskGroup.id | ||||
| if(taskGroupsKeys.includes(taskGroupId.toString())) { | |||||
| updatedTaskGroups[taskGroupId] = {...updatedTaskGroups[taskGroupId], percentAllocation: groupAllocation?.percentage} | |||||
| if (taskGroupsKeys.includes(taskGroupId.toString())) { | |||||
| updatedTaskGroups[taskGroupId] = { ...updatedTaskGroups[taskGroupId], percentAllocation: groupAllocation?.percentage } | |||||
| } | } | ||||
| }) | }) | ||||
| const percentageToZeroGroupIds = difference(taskGroupsKeys.map(key => parseFloat(key)), selectedTaskTemplate?.groupAllocations.map(groupAllocation => groupAllocation.taskGroup.id)!!) | const percentageToZeroGroupIds = difference(taskGroupsKeys.map(key => parseFloat(key)), selectedTaskTemplate?.groupAllocations.map(groupAllocation => groupAllocation.taskGroup.id)!!) | ||||
| percentageToZeroGroupIds.forEach((percentageToZeroGroupId) => { | percentageToZeroGroupIds.forEach((percentageToZeroGroupId) => { | ||||
| updatedTaskGroups[percentageToZeroGroupId] = {...updatedTaskGroups[percentageToZeroGroupId], percentAllocation: 0} | |||||
| updatedTaskGroups[percentageToZeroGroupId] = { ...updatedTaskGroups[percentageToZeroGroupId], percentAllocation: 0 } | |||||
| }) | }) | ||||
| setValue("taskGroups", updatedTaskGroups) | setValue("taskGroups", updatedTaskGroups) | ||||
| if (Object.values(updatedTaskGroups).reduce((acc, value) => acc + value.percentAllocation, 0) === 100) clearErrors("taskGroups") | if (Object.values(updatedTaskGroups).reduce((acc, value) => acc + value.percentAllocation, 0) === 100) clearErrors("taskGroups") | ||||
| else setError("taskGroups", {message: "Task Groups value is not invalid", type: "invalid"}) | |||||
| else setError("taskGroups", { message: "Task Groups value is not invalid", type: "invalid" }) | |||||
| } | } | ||||
| setValue("taskTemplateId", selectedTaskTemplateId) | |||||
| const taskList = | const taskList = | ||||
| selectedTaskTemplateId === "All" | selectedTaskTemplateId === "All" | ||||
| ? tasks | ? tasks | ||||
| @@ -176,7 +179,26 @@ const TaskSetup: React.FC<Props> = ({ | |||||
| }; | }; | ||||
| }, {}); | }, {}); | ||||
| setValue("taskGroups", newTaskGroups); | |||||
| // update the "manhour allocation by grade by stage" by task template | |||||
| const taskGroupsKeys = Object.keys(newTaskGroups) | |||||
| const selectedTaskTemplate = taskTemplates.find( | |||||
| (template) => template.id === selectedTaskTemplateId, | |||||
| ) | |||||
| selectedTaskTemplate?.groupAllocations.forEach((groupAllocation) => { | |||||
| const taskGroupId = groupAllocation.taskGroup.id | |||||
| if (taskGroupsKeys.includes(taskGroupId.toString())) { | |||||
| newTaskGroups[taskGroupId] = { ...newTaskGroups[taskGroupId], percentAllocation: groupAllocation?.percentage } | |||||
| } | |||||
| }) | |||||
| const percentageToZeroGroupIds = difference(taskGroupsKeys.map(key => parseFloat(key)), selectedTaskTemplate?.groupAllocations.map(groupAllocation => groupAllocation.taskGroup.id)!!) | |||||
| percentageToZeroGroupIds.forEach((percentageToZeroGroupId) => { | |||||
| newTaskGroups[percentageToZeroGroupId] = { ...newTaskGroups[percentageToZeroGroupId], percentAllocation: 0 } | |||||
| }) | |||||
| setValue("taskGroups", newTaskGroups) | |||||
| if (Object.values(newTaskGroups).reduce((acc, value) => acc + value.percentAllocation, 0) === 100) clearErrors("taskGroups") | |||||
| else setError("taskGroups", { message: "Task Groups value is not invalid", type: "invalid" }) | |||||
| }} | }} | ||||
| allItemsLabel={t("Task Pool")} | allItemsLabel={t("Task Pool")} | ||||
| selectedItemsLabel={t("Project Task List")} | selectedItemsLabel={t("Project Task List")} | ||||
| @@ -46,6 +46,12 @@ const ProjectSearch: React.FC<Props> = ({ projects, projectCategories }) => { | |||||
| type: "select", | type: "select", | ||||
| options: uniq(projects.map((project) => project.team)), | options: uniq(projects.map((project) => project.team)), | ||||
| }, | }, | ||||
| { | |||||
| label: t("Status"), | |||||
| paramName: "status", | |||||
| type: "select", | |||||
| options: uniq(projects.map((project) => project.status)), | |||||
| }, | |||||
| ], | ], | ||||
| [t, projectCategories, projects], | [t, projectCategories, projects], | ||||
| ); | ); | ||||
| @@ -74,6 +80,7 @@ const ProjectSearch: React.FC<Props> = ({ projects, projectCategories }) => { | |||||
| { name: "category", label: t("Project Category") }, | { name: "category", label: t("Project Category") }, | ||||
| { name: "team", label: t("Team") }, | { name: "team", label: t("Team") }, | ||||
| { name: "client", label: t("Client") }, | { name: "client", label: t("Client") }, | ||||
| { name: "status", label: t("Status") }, | |||||
| ], | ], | ||||
| [t, onProjectClick], | [t, onProjectClick], | ||||
| ); | ); | ||||
| @@ -90,7 +97,8 @@ const ProjectSearch: React.FC<Props> = ({ projects, projectCategories }) => { | |||||
| p.name.toLowerCase().includes(query.name.toLowerCase()) && | p.name.toLowerCase().includes(query.name.toLowerCase()) && | ||||
| (query.client === "All" || p.client === query.client) && | (query.client === "All" || p.client === query.client) && | ||||
| (query.category === "All" || p.category === query.category) && | (query.category === "All" || p.category === query.category) && | ||||
| (query.team === "All" || p.team === query.team), | |||||
| (query.team === "All" || p.team === query.team) && | |||||
| (query.status === "All" || p.status === query.status), | |||||
| ), | ), | ||||
| ); | ); | ||||
| }} | }} | ||||