@@ -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), | |||||
), | ), | ||||
); | ); | ||||
}} | }} | ||||