浏览代码

update project

tags/Baseline_30082024_FRONTEND_UAT
cyril.tsui 1年前
父节点
当前提交
8479f6fd10
共有 9 个文件被更改,包括 198 次插入92 次删除
  1. +7
    -4
      src/app/(main)/layout.tsx
  2. +3
    -0
      src/app/api/customer/index.ts
  3. +6
    -1
      src/app/api/projects/actions.ts
  4. +1
    -0
      src/app/api/projects/index.ts
  5. +6
    -2
      src/app/api/subsidiary/index.ts
  6. +17
    -8
      src/components/CreateProject/CreateProject.tsx
  7. +117
    -66
      src/components/CreateProject/ProjectClientDetails.tsx
  8. +32
    -10
      src/components/CreateProject/TaskSetup.tsx
  9. +9
    -1
      src/components/ProjectSearch/ProjectSearch.tsx

+ 7
- 4
src/app/(main)/layout.tsx 查看文件

@@ -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>
</> </>
); );


+ 3
- 0
src/app/api/customer/index.ts 查看文件

@@ -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 {


+ 6
- 1
src/app/api/projects/actions.ts 查看文件

@@ -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;


+ 1
- 0
src/app/api/projects/index.ts 查看文件

@@ -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 {


+ 6
- 2
src/app/api/subsidiary/index.ts 查看文件

@@ -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 {


+ 17
- 8
src/components/CreateProject/CreateProject.tsx 查看文件

@@ -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"))
} }


+ 117
- 66
src/components/CreateProject/ProjectClientDetails.tsx 查看文件

@@ -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" }}>


+ 32
- 10
src/components/CreateProject/TaskSetup.tsx 查看文件

@@ -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")}


+ 9
- 1
src/components/ProjectSearch/ProjectSearch.tsx 查看文件

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


正在加载...
取消
保存