diff --git a/epictrack-api/src/api/services/task.py b/epictrack-api/src/api/services/task.py
index b70c032fa..0923596db 100644
--- a/epictrack-api/src/api/services/task.py
+++ b/epictrack-api/src/api/services/task.py
@@ -65,12 +65,10 @@ def update_task_event(cls, data: dict, task_event_id) -> TaskEvent:
"Only team members can be assigned to a task"
)
task_event.update(data, commit=False)
- if data.get("assignee_ids"):
- cls._handle_assignees(data.get("assignee_ids"), [task_event.id])
- if data.get("responsibility_ids"):
- cls._handle_responsibilities(
- data.get("responsibility_ids"), [task_event.id]
- )
+ cls._handle_assignees(data.get("assignee_ids"), [task_event.id])
+ cls._handle_responsibilities(
+ data.get("responsibility_ids"), [task_event.id]
+ )
db.session.commit()
return task_event
diff --git a/epictrack-web/src/components/shared/controlledInputComponents/ControlledMultiSelect/Option.tsx b/epictrack-web/src/components/shared/controlledInputComponents/ControlledMultiSelect/Option.tsx
new file mode 100644
index 000000000..59492f4bd
--- /dev/null
+++ b/epictrack-web/src/components/shared/controlledInputComponents/ControlledMultiSelect/Option.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+import { Box, Checkbox } from "@mui/material";
+import { components, OptionProps } from "react-select";
+
+const Option = ({
+ getStyles,
+ isDisabled,
+ isFocused,
+ children,
+ innerProps,
+ isMulti,
+ ...rest
+}: OptionProps) => {
+ const [isSelected, setIsSelected] = React.useState(false);
+ const { filterProps } = rest.selectProps;
+
+ React.useEffect(() => {
+ if (filterProps?.selectedOptions && filterProps.getOptionValue) {
+ const val = filterProps.getOptionValue(rest.data);
+ setIsSelected(filterProps?.selectedOptions.indexOf(val) > -1);
+ }
+ }, [filterProps?.selectedOptions]);
+
+ return (
+
+
+
+ {children}
+
+
+ );
+};
+
+export default Option;
diff --git a/epictrack-web/src/components/shared/controlledInputComponents/ControlledMultiSelect/index.tsx b/epictrack-web/src/components/shared/controlledInputComponents/ControlledMultiSelect/index.tsx
new file mode 100644
index 000000000..5cb6b132b
--- /dev/null
+++ b/epictrack-web/src/components/shared/controlledInputComponents/ControlledMultiSelect/index.tsx
@@ -0,0 +1,164 @@
+import React from "react";
+import { FormHelperText } from "@mui/material";
+import { Controller, useFormContext } from "react-hook-form";
+import Select, { CSSObjectWithLabel } from "react-select";
+import { Palette } from "../../../../styles/theme";
+import Option from "./Option";
+
+type IFormInputProps = {
+ placeholder?: string;
+ name: string;
+ options: Array;
+ defaultValue?: string[] | undefined;
+ isMulti?: boolean;
+ getOptionLabel: (option: any) => string;
+ getOptionValue: (option: any) => string;
+ helperText?: string | undefined;
+ disabled?: boolean;
+ onHandleChange?: (val: any) => void;
+ closeMenuOnSelect?: boolean;
+ hideSelectedOptions?: boolean;
+ // menuPortalTarget: HTMLElement | undefined;
+};
+
+const ControlledSelectV2: React.ForwardRefRenderFunction<
+ HTMLDivElement,
+ IFormInputProps
+> = (
+ {
+ placeholder,
+ name,
+ options,
+ getOptionLabel,
+ getOptionValue,
+ isMulti,
+ disabled,
+ helperText,
+ onHandleChange,
+ // menuPortalTarget,
+ closeMenuOnSelect,
+ hideSelectedOptions,
+ defaultValue,
+ ...otherProps
+ },
+ ref
+) => {
+ const [selectedOptions, setSelectedOptions] = React.useState([]);
+
+ React.useEffect(() => {
+ setSelectedOptions(Array.from(new Set(defaultValue)));
+ }, [defaultValue]);
+
+ const handleChange = (item: any) => {
+ const selected: Array = [];
+ item.map((o: any) => {
+ const val = getOptionValue(o);
+ selected.push(val);
+ });
+ setSelectedOptions(Array.from(new Set(selected)));
+ };
+
+ const {
+ control,
+ formState: { errors, defaultValues },
+ } = useFormContext();
+ return (
+ {
+ const { onChange, value, ref } = field;
+ return (
+ <>
+
+ {helperText && (
+
+ {String(errors[name]?.message || "")}
+
+ )}
+ >
+ );
+ }}
+ />
+ );
+};
+
+export default React.forwardRef(ControlledSelectV2);
diff --git a/epictrack-web/src/components/shared/filterSelect/type.ts b/epictrack-web/src/components/shared/filterSelect/type.ts
index f8767abb6..7af39753f 100644
--- a/epictrack-web/src/components/shared/filterSelect/type.ts
+++ b/epictrack-web/src/components/shared/filterSelect/type.ts
@@ -15,11 +15,15 @@ declare module "react-select/dist/declarations/src/Select" {
// Marking as optional here to not raise errors for ControlledSelect
// Make sure to add for FilterSelect
filterProps?: {
- applyFilters: () => void;
- clearFilters: () => void;
+ applyFilters?: () => void;
+ clearFilters?: () => void;
selectedOptions: any[];
- onCancel: () => void;
- variant: "inline" | "bar";
+ options?: any[];
+ onCancel?: () => void;
+ variant?: "inline" | "bar";
+ getOptionLabel?: (option: any) => string;
+ getOptionValue?: (option: any) => string;
+ label?: string;
};
filterAppliedCallback?: (value?: string[] | string) => void;
filterClearedCallback?: (value?: [] | "") => void;
diff --git a/epictrack-web/src/components/workPlan/event/EventList.tsx b/epictrack-web/src/components/workPlan/event/EventList.tsx
index 5a93b0243..3036da643 100644
--- a/epictrack-web/src/components/workPlan/event/EventList.tsx
+++ b/epictrack-web/src/components/workPlan/event/EventList.tsx
@@ -696,7 +696,7 @@ const EventList = () => {
onDialogClose(event, reason)}
disableEscapeKeyDown
fullWidth
diff --git a/epictrack-web/src/components/workPlan/task/TaskForm.tsx b/epictrack-web/src/components/workPlan/task/TaskForm.tsx
index e8d7798be..072c87247 100644
--- a/epictrack-web/src/components/workPlan/task/TaskForm.tsx
+++ b/epictrack-web/src/components/workPlan/task/TaskForm.tsx
@@ -27,6 +27,7 @@ import RichTextEditor from "../../shared/richTextEditor";
import { dateUtils } from "../../../utils";
import { EVENT_TYPE } from "../phase/type";
import { EventContext } from "../event/EventContext";
+import ControlledMultiSelect from "../../shared/controlledInputComponents/ControlledMultiSelect";
const schema = yup.object().shape({
name: yup.string().required("Name is required"),
@@ -68,6 +69,7 @@ const TaskForm = ({
formState: { errors },
reset,
control,
+ setValue,
} = methods;
useEffect(() => {
@@ -186,6 +188,15 @@ const TaskForm = ({
}
};
+ const endDateChangeHandler = (endDate: any) => {
+ if (startDateRef?.current as any) {
+ const startDate = (startDateRef?.current as any)["value"];
+ const dateDiff = dateUtils.diff(endDate, startDate, "days");
+ (numberOfDaysRef.current as any)["value"] = dateDiff;
+ setValue("number_of_days", dateDiff);
+ }
+ };
+
return (
<>
@@ -209,7 +220,7 @@ const TaskForm = ({
}}
>
- Task Title
+ Title
- Days
+ Number of Days
- End Date
- End Date
+ (
+
+ {
+ const d = event ? event["$d"] : null;
+ endDateChangeHandler(d);
+ }}
+ sx={{ display: "block" }}
+ />
+
+ )}
/>
-
- Status
+
+ Progress
Assigned
- parseInt(p))}
+ closeMenuOnSelect={false}
+ hideSelectedOptions={false}
+ defaultValue={taskEvent?.assignee_ids?.map((p) => p.toString())}
options={assignees || []}
getOptionValue={(o: Staff) => o?.id.toString()}
getOptionLabel={(o: Staff) => o.full_name}
{...register("assignee_ids")}
- >
+ >
Responsibility
-
- parseInt(p)
+ p.toString()
)}
options={responsibilities || []}
getOptionValue={(o: ListType) => o?.id.toString()}
getOptionLabel={(o: ListType) => o.name}
{...register("responsibility_ids")}
- >
+ >
Notes