|
- "use client";
-
- import Card from "@mui/material/Card";
- import CardContent from "@mui/material/CardContent";
- import Grid from "@mui/material/Grid";
- import Typography from "@mui/material/Typography";
- import { useTranslation } from "react-i18next";
- import TransferList from "../TransferList";
- import Button from "@mui/material/Button";
- import React, { SyntheticEvent, useCallback, useMemo, useState } from "react";
- import CardActions from "@mui/material/CardActions";
- import RestartAlt from "@mui/icons-material/RestartAlt";
- import FormControl from "@mui/material/FormControl";
- import Select, { SelectChangeEvent } from "@mui/material/Select";
- import MenuItem from "@mui/material/MenuItem";
- import InputLabel from "@mui/material/InputLabel";
- import { Task, TaskTemplate } from "@/app/api/tasks";
- import { useFormContext } from "react-hook-form";
- import { CreateProjectInputs, ManhourAllocation } from "@/app/api/projects/actions";
- import isNumber from "lodash/isNumber";
- import intersectionWith from "lodash/intersectionWith";
- import { difference } from "lodash";
- import { Autocomplete, TextField } from "@mui/material";
-
- interface Props {
- allTasks: Task[];
- taskTemplates: TaskTemplate[];
- isActive: boolean;
- }
-
- const TaskSetup: React.FC<Props> = ({
- allTasks: tasks,
- taskTemplates,
- isActive,
- }) => {
- const { t } = useTranslation();
- const { setValue, watch, clearErrors, setError, formState: { defaultValues } } = useFormContext<CreateProjectInputs>();
- const currentTaskGroups = watch("taskGroups");
- const currentTaskIds = Object.values(currentTaskGroups).reduce<Task["id"][]>(
- (acc, group) => {
- return group.taskIds && group.taskIds.length > 0 ? [...acc, ...group.taskIds] : acc;
- },
- [],
- );
-
- const onReset = useCallback(() => {
- setValue("taskGroups", {});
- }, [setValue]);
-
- const [selectedTaskTemplateId, setSelectedTaskTemplateId] = useState<
- "All" | number
- >(watch("taskTemplateId") ?? "All");
- const onSelectTaskTemplate = useCallback(
- (event: SyntheticEvent<Element, Event>, value: NonNullable<{id: number | string, name: string}>) => {
- if (value.id === "All" || isNumber(value.id)) {
- setSelectedTaskTemplateId(value.id);
- // onReset();
- }
- },
- [onReset],
- );
-
- const items = useMemo(() => {
- const selectedTaskTemplate = taskTemplates.find(
- (template) => template.id === selectedTaskTemplateId,
- )
-
- if (selectedTaskTemplateId !== "All" && selectedTaskTemplateId !== watch("taskTemplateId")) {
-
- // update the "manhour allocation by grade" by task template
- const updatedManhourPercentageByGrade: ManhourAllocation = watch("manhourPercentageByGrade")
- selectedTaskTemplate?.gradeAllocations.forEach((gradeAllocation) => {
- updatedManhourPercentageByGrade[gradeAllocation.grade.id] = gradeAllocation?.percentage
- })
-
- setValue("manhourPercentageByGrade", updatedManhourPercentageByGrade)
- if (Object.values(updatedManhourPercentageByGrade).reduce((acc, value) => acc + value, 0) === 100) clearErrors("manhourPercentageByGrade")
- else setError("manhourPercentageByGrade", { message: "manhourPercentageByGrade value is not valid", type: "invalid" })
-
- // update the "manhour allocation by grade by stage" by task template
- const updatedTaskGroups = watch("taskGroups")
- const taskGroupsKeys = Object.keys(updatedTaskGroups)
- selectedTaskTemplate?.groupAllocations.forEach((groupAllocation) => {
- const taskGroupId = groupAllocation.taskGroup.id
- 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)!!)
- percentageToZeroGroupIds.forEach((percentageToZeroGroupId) => {
- updatedTaskGroups[percentageToZeroGroupId] = { ...updatedTaskGroups[percentageToZeroGroupId], percentAllocation: 0 }
- })
-
- setValue("taskGroups", updatedTaskGroups)
- 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" })
- }
-
- setValue("taskTemplateId", selectedTaskTemplateId)
-
- const taskList =
- selectedTaskTemplateId === "All"
- ? tasks
- : selectedTaskTemplate?.tasks || tasks;
-
-
- return taskList.map((t) => ({
- id: t.id,
- label: t.name,
- group: t.taskGroup,
- }));
- }, [tasks, selectedTaskTemplateId, taskTemplates]);
-
- const selectedItems = useMemo(() => {
- return intersectionWith(
- tasks,
- currentTaskIds,
- (task, taskId) => task.id === taskId,
- ).map((t) => ({ id: t.id, label: t.name, group: t.taskGroup }));
- }, [currentTaskIds, tasks]);
-
- return (
- <Card sx={{ display: isActive ? "block" : "none" }}>
- <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
- <Typography variant="overline" display="block" marginBlockEnd={1}>
- {t("Task List Setup")}
- </Typography>
- <Grid
- container
- spacing={2}
- columns={{ xs: 6, sm: 12 }}
- marginBlockEnd={1}
- >
- <Grid item xs={6}>
- <Autocomplete
- disableClearable
- // disablePortal
- noOptionsText={t("No Task List Source")}
- value={taskTemplates.find(taskTemplate => taskTemplate.id === selectedTaskTemplateId)}
- options={[{id: "All", name: t("All tasks")}, ...taskTemplates.map(taskTemplate => ({id: taskTemplate.id, name: taskTemplate.name}))]}
- getOptionLabel={(taskTemplate) => taskTemplate.name}
- isOptionEqualToValue={(option, value) => option?.id === value?.id}
- renderOption={(params, option) => {
- return (
- <MenuItem {...params} key={option.id} value={option.id}>
- {option.name}
- </MenuItem>
- );
- }}
- onChange={onSelectTaskTemplate}
- renderInput={(params) => <TextField {...params} variant="outlined" label={t("Task List Source")} />}
- />
- </Grid>
- </Grid>
- <TransferList
- allItems={items}
- selectedItems={selectedItems}
- onChange={(selectedTasks) => {
- const newTaskGroups = selectedTasks.reduce<
- CreateProjectInputs["taskGroups"]
- >((acc, item) => {
- if (!item.group) {
- // TODO: this should not happen (all tasks are part of a group)
- return acc;
- }
- if (!acc[item.group.id]) {
- return {
- ...acc,
- [item.group.id]: {
- taskIds: [item.id],
- percentAllocation:
- currentTaskGroups[item.group.id]?.percentAllocation || 0,
- },
- };
- }
- return {
- ...acc,
- [item.group.id]: {
- ...acc[item.group.id],
- taskIds: [...acc[item.group.id].taskIds, item.id],
- },
- };
- }, {});
-
- // 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")}
- selectedItemsLabel={t("Project Task List")}
- />
- {/* <CardActions sx={{ justifyContent: "flex-end" }}>
- <Button variant="text" startIcon={<RestartAlt />} onClick={onReset}>
- {t("Reset")}
- </Button>
- </CardActions> */}
- </CardContent>
- </Card>
- );
- };
-
- export default TaskSetup;
|