Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { useMemo } from "react";
import { observer } from "mobx-react";
import { Button, IconChevronLeft, IconChevronRight, Tooltip } from "@humansignal/ui";
import { cn } from "../../utils/bem";
import { guidGenerator } from "../../utils/unique";
import { isDefined } from "../../utils/utilities";
import { FF_LEAP_1173, FF_TASK_COUNT_FIX, isFF } from "../../utils/feature-flags";
import "./CurrentTask.scss";
 
// Manager roles that can force-skip unskippable tasks (OW=Owner, AD=Admin, MA=Manager)
const MANAGER_ROLES = ["OW", "AD", "MA"];
 
export const CurrentTask = observer(({ store }) => {
  const currentIndex = useMemo(() => {
    return store.taskHistory.findIndex((x) => x.taskId === store.task.id) + 1;
  }, [store.taskHistory]);
 
  const historyEnabled = store.hasInterface("topbar:prevnext");
  const task = store.task;
  const taskAllowSkip = task?.allow_skip !== false;
  const userRole = window.APP_SETTINGS?.user?.role;
  const hasForceSkipPermission = MANAGER_ROLES.includes(userRole);
  const canSkipOrPostpone = taskAllowSkip || hasForceSkipPermission;
 
  // Check if user has submitted an annotation (pk is defined means annotation is in database)
  const hasSubmittedAnnotation = isDefined(store.annotationStore.selected.pk);
 
  // If task cannot be skipped and user doesn't have force_skip, also disable postpone
  // Note: store.hasInterface("postpone") is set by lsf-sdk based on task.allow_postpone from API
  const canPostpone =
    !hasSubmittedAnnotation &&
    !store.canGoNextTask &&
    (!isFF(FF_LEAP_1173) || store.hasInterface("skip")) &&
    !store.hasInterface("review") &&
    store.hasInterface("postpone") &&
    canSkipOrPostpone;
 
  // For unskippable tasks, force user to submit annotation before navigating
  // Block both history navigation (next task) and postpone if no annotation submitted
  const requiresAnnotationSubmission = !taskAllowSkip && !hasForceSkipPermission && !hasSubmittedAnnotation;
  const canNavigateNext = store.canGoNextTask && !requiresAnnotationSubmission;
  const canPostponeTask = canPostpone && !requiresAnnotationSubmission;
 
  // Memoized messages for previous button
  const prevButtonMessage = useMemo(() => {
    return !store.canGoPrevTask ? "没有上一任务" : "上一任务";
  }, [store.canGoPrevTask]);
 
  // Memoized messages for next button
  const nextButtonMessage = useMemo(() => {
    if (requiresAnnotationSubmission) {
      return "提交标注以继续";
    }
    if (canNavigateNext) {
      return "下一任务";
    }
    if (canPostponeTask) {
      return "推迟任务";
    }
    if (!canSkipOrPostpone) {
      return "无法推迟:任务不可跳过";
    }
    return "没有下一任务";
  }, [requiresAnnotationSubmission, canNavigateNext, canPostponeTask, canSkipOrPostpone]);
 
  return (
    <div className={cn("bottombar").elem("section").toClassName()}>
      <div className={cn("current-task").mod({ "with-history": historyEnabled }).toClassName()}>
        <div className={cn("current-task").elem("task-id").toClassName()}>
          {store.task.id ?? guidGenerator()}
          {historyEnabled &&
            (isFF(FF_TASK_COUNT_FIX) ? (
              <div className={cn("current-task").elem("task-count").toClassName()}>
                {store.queuePosition} / {store.queueTotal}
              </div>
            ) : (
              <div className={cn("current-task").elem("task-count").toClassName()}>
                {currentIndex} / {store.taskHistory.length}
              </div>
            ))}
        </div>
        {historyEnabled && (
          <div className={cn("current-task").elem("history-controls").toClassName()}>
            <Tooltip title={prevButtonMessage} alignment="bottom-center">
              <Button
                variant="neutral"
                data-testid="prev-task"
                aria-label={prevButtonMessage}
                disabled={!historyEnabled || !store.canGoPrevTask}
                onClick={store.prevTask}
              >
                <IconChevronLeft />
              </Button>
            </Tooltip>
            <Tooltip title={nextButtonMessage} alignment="bottom-center">
              <Button
                data-testid="next-task"
                aria-label={nextButtonMessage}
                disabled={!canNavigateNext && !canPostponeTask}
                onClick={canNavigateNext ? store.nextTask : store.postponeTask}
                variant={!canNavigateNext && canPostponeTask ? "primary" : "neutral"}
              >
                <IconChevronRight />
              </Button>
            </Tooltip>
          </div>
        )}
      </div>
    </div>
  );
});