export function TaskSelection()

in frontend/src/components/taskSelection/index.js [45:372]


export function TaskSelection({ project, type, loading }: Object) {
  const user = useSelector((state) => state.auth.get('userDetails'));
  const userOrgs = useSelector((state) => state.auth.get('organisations'));
  const lockedTasks = useGetLockedTasks();
  const dispatch = useDispatch();
  const [tasks, setTasks] = useState();
  const [activities, setActivities] = useState();
  const [contributions, setContributions] = useState();
  const [isValidationAllowed, setIsValidationAllowed] = useState(undefined);
  const [zoomedTaskId, setZoomedTaskId] = useState(null);
  const [activeSection, setActiveSection] = useState(null);
  const [selected, setSelectedTasks] = useState([]);
  const [mapInit, setMapInit] = useState(false);
  const [randomTask, setRandomTask] = useState([]);
  const [taskAction, setTaskAction] = useState('mapATask');
  const [activeStatus, setActiveStatus] = useState(null);
  const [activeUser, setActiveUser] = useState(null);
  const [textSearch, setTextSearch] = useQueryParam('search', StringParam);
  const defaultUpdateInterval = 60000;
  const [updateInterval, setUpdateInterval] = useState(defaultUpdateInterval);
  useSetProjectPageTitleTag(project);

  // get teams the user is part of
  const [userTeamsError, userTeamsLoading, userTeams] = useFetch(
    `teams/?omitMemberList=true&member=${user.id}`,
    user.id !== undefined,
  );
  //eslint-disable-next-line
  const [priorityAreasError, priorityAreasLoading, priorityAreas] = useFetch(
    `projects/${project.projectId}/queries/priority-areas/`,
    project.projectId !== undefined,
  );

  const getActivities = useCallback((id) => {
    if (id) {
      fetchLocalJSONAPI(`projects/${id}/activities/latest/`)
        .then((res) => setActivities(res))
        .catch((e) => console.log(e));
    }
  }, []);

  const getContributions = useCallback((id) => {
    if (id) {
      fetchLocalJSONAPI(`projects/${id}/contributions/`)
        .then((res) => setContributions(res))
        .catch((e) => console.log(e));
    }
  }, []);

  // fetch activities and contributions when the component is started
  useEffect(() => {
    getActivities(project.projectId);
    getContributions(project.projectId);
  }, [getActivities, getContributions, project.projectId]);
  // refresh activities each 60 seconds if page is visible to user
  useInterval(() => {
    if (document.visibilityState === 'visible') {
      getActivities(project.projectId);
      if (activeSection === 'contributions') {
        getContributions(project.projectId);
      }
      if (updateInterval !== defaultUpdateInterval) setUpdateInterval(defaultUpdateInterval);
    } else {
      if (updateInterval !== 1000) setUpdateInterval(1000);
    }
  }, updateInterval);

  const latestTasks = useRef(tasks);
  // it's needed to avoid the following useEffect to be triggered every time the tasks change
  useEffect(() => {
    latestTasks.current = tasks;
  });
  // refresh the task status on the map each time the activities are updated
  useEffect(() => {
    if (latestTasks.current && activities) {
      setTasks(updateTasksStatus(latestTasks.current, activities));
    }
  }, [latestTasks, activities]);

  // we use this in order to only trigger the following useEffect if there are
  // new tasks on activities endpoint data
  const latestActivities = useMemoCompare(
    activities,
    (prev) => prev && prev.activity.length === activities.activity.length,
  );

  // update tasks geometry if there are new tasks (caused by task splits)
  useEffect(() => {
    if (latestActivities && latestActivities.activity) {
      fetchLocalJSONAPI(`projects/${project.projectId}/tasks/`).then((res) =>
        setTasks(updateTasksStatus(res, latestActivities)),
      );
    }
  }, [latestActivities, project.projectId]);

  // show the tasks tab when the page loads if the user has already contributed
  // to the project. If no, show the instructions tab.
  useEffect(() => {
    if (contributions && contributions.userContributions && activeSection === null) {
      const currentUserContributions = contributions.userContributions.filter(
        (u) => u.username === user.username,
      );
      if (textSearch || (user.isExpert && currentUserContributions.length > 0)) {
        setActiveSection('tasks');
      } else {
        setActiveSection('instructions');
      }
    }
  }, [contributions, user.username, user, activeSection, textSearch]);

  useEffect(() => {
    if (
      project.hasOwnProperty('teams') &&
      !userTeamsError &&
      !userTeamsLoading &&
      userTeams !== undefined
    ) {
      setIsValidationAllowed(userCanValidate(user, project, userTeams.teams));
    }
  }, [userTeams, userTeamsError, userTeamsLoading, project, user]);

  useEffect(() => {
    // run it only when the component is initialized
    // it checks if the user has tasks locked on the project and suggests to resume them
    if (!mapInit && activities && activities.activity && user.username && !userTeamsLoading) {
      const lockedByCurrentUser = activities.activity
        .filter((i) => i.taskStatus.startsWith('LOCKED_FOR_'))
        .filter((i) => i.actionBy === user.username);
      if (lockedByCurrentUser.length) {
        const userLockedTasks = lockedByCurrentUser.map((i) => i.taskId);
        setSelectedTasks(userLockedTasks);
        setTaskAction(
          lockedByCurrentUser[0].taskStatus === 'LOCKED_FOR_MAPPING'
            ? 'resumeMapping'
            : 'resumeValidation',
        );
        dispatch({ type: 'SET_LOCKED_TASKS', tasks: userLockedTasks });
        dispatch({ type: 'SET_PROJECT', project: project.projectId });
        dispatch({ type: 'SET_TASKS_STATUS', status: lockedByCurrentUser[0].taskStatus });
      } else {
        // select task if the textSearch query param is a valid taskId
        if (
          textSearch &&
          Number(textSearch) &&
          activities.activity.map((i) => i.taskId).includes(Number(textSearch))
        ) {
          setSelectedTasks([Number(textSearch)]);
          const currentStatus = activities.activity.filter(
            (i) => i.taskId === Number(textSearch),
          )[0].taskStatus;
          setTaskAction(getTaskAction(user, project, currentStatus, userTeams.teams, userOrgs));
        } else {
          // otherwise we check if the user can map or validate the project
          setTaskAction(getTaskAction(user, project, null, userTeams.teams, userOrgs));
        }
      }
      setMapInit(true);
    }
  }, [
    lockedTasks,
    dispatch,
    activities,
    user.username,
    mapInit,
    project,
    user,
    userTeams.teams,
    userTeamsLoading,
    userOrgs,
    textSearch,
  ]);

  // chooses a random task to the user
  useEffect(() => {
    if (activities && activities.activity) {
      setRandomTask([getRandomTaskByAction(activities.activity, taskAction)]);
    }
  }, [activities, taskAction]);

  function selectTask(selection, status = null, selectedUser = null) {
    // if selection is an array, just update the state
    if (typeof selection === 'object') {
      setSelectedTasks(selection);
      setTaskAction(getTaskAction(user, project, status, userTeams.teams, userOrgs));
    } else {
      // unselecting tasks
      if (selected.includes(selection)) {
        // if there is only one task selected, just clear the selection
        if (selection.length === 1) {
          setSelectedTasks([]);
          setTaskAction(getTaskAction(user, project, null, userTeams.teams, userOrgs));
        } else {
          // if there are multiple tasks selected, remove the clicked one
          setSelectedTasks(selected.filter((i) => i !== selection));
        }
      } else {
        // if there is some task selected to validation and the user selects
        //  another MAPPED task, add the new task to the selected array
        if (taskAction === 'validateSelectedTask' && status === 'MAPPED') {
          setSelectedTasks(selected.concat([selection]));
        } else {
          setSelectedTasks([selection]);
          if (lockedTasks.get('tasks').includes(selection)) {
            setTaskAction(
              lockedTasks.get('status') === 'LOCKED_FOR_MAPPING'
                ? 'resumeMapping'
                : 'resumeValidation',
            );
          } else {
            setTaskAction(getTaskAction(user, project, status, userTeams.teams, userOrgs));
          }
        }
      }
    }
    if (selectedUser === null) {
      // when a task is selected directly on the map or in the task list,
      // reset the activeUser and activeStatus in order to disable the user highlight
      // on contributions tab
      setActiveUser(null);
      setActiveStatus(null);
    } else {
      // when a user is selected in the contributions tab, update the activeUser,
      // so we can highlight it there
      setActiveUser(selectedUser);
      setActiveStatus(status);
    }
  }

  return (
    <div>
      <div className="cf vh-minus-200-ns">
        {!userTeamsLoading && ['mappingIsComplete', 'selectAnotherProject'].includes(taskAction) && (
          <Popup modal open closeOnEscape={true} closeOnDocumentClick={true}>
            {(close) => (
              <UserPermissionErrorContent
                project={project}
                userLevel={user.mappingLevel}
                close={close}
              />
            )}
          </Popup>
        )}
        <div className="w-100 w-50-ns fl pt3 overflow-y-scroll-ns vh-minus-200-ns h-100">
          <div className="pl4-l pl2 pr2">
            <ReactPlaceholder
              showLoadingAnimation={true}
              rows={3}
              ready={typeof project.projectId === 'number' && project.projectId > 0}
            >
              <ProjectHeader project={project} />
              <div className="cf">
                <TabSelector activeSection={activeSection} setActiveSection={setActiveSection} />
                <div className="pt3">
                  <div className={`${activeSection !== 'tasks' ? 'dn' : ''}`}>
                    <TaskList
                      project={project}
                      tasks={tasks}
                      userCanValidate={isValidationAllowed}
                      updateActivities={getActivities}
                      selectTask={selectTask}
                      selected={selected}
                      textSearch={textSearch}
                      setTextSearch={setTextSearch}
                      setZoomedTaskId={setZoomedTaskId}
                      userContributions={contributions && contributions.userContributions}
                    />
                  </div>
                  {activeSection === 'instructions' ? (
                    <>
                      <ProjectInstructions
                        instructions={project.projectInfo && project.projectInfo.instructions}
                      />
                      <ChangesetCommentTags tags={project.changesetComment} />
                    </>
                  ) : null}
                  {activeSection === 'contributions' ? (
                    <Contributions
                      project={project}
                      selectTask={selectTask}
                      tasks={tasks}
                      contribsData={contributions}
                      activeUser={activeUser}
                      activeStatus={activeStatus}
                    />
                  ) : null}
                </div>
              </div>
            </ReactPlaceholder>
          </div>
        </div>
        <div className="w-100 w-50-ns fl h-100 relative">
          <ReactPlaceholder
            showLoadingAnimation={true}
            type={'media'}
            rows={26}
            delay={200}
            ready={
              typeof project === 'object' &&
              typeof tasks === 'object' &&
              mapInit &&
              !priorityAreasLoading
            }
          >
            <TasksMap
              mapResults={tasks}
              projectId={project.projectId}
              error={typeof project !== 'object'}
              loading={typeof project !== 'object'}
              className="dib w-100 fl h-100-ns vh-75"
              zoomedTaskId={zoomedTaskId}
              selectTask={selectTask}
              selected={selected}
              taskBordersOnly={false}
              priorityAreas={priorityAreas}
              animateZoom={false}
            />
            <TasksMapLegend />
          </ReactPlaceholder>
        </div>
      </div>
      <div className="cf w-100 bt b--grey-light fixed bottom-0 left-0 z-4">
        <ReactPlaceholder
          showLoadingAnimation={true}
          rows={3}
          delay={500}
          ready={typeof project.projectId === 'number' && project.projectId > 0}
        >
          <Suspense fallback={<div>Loading...</div>}>