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