Q150: work can be sent back, reopened, moved to backlog, or blocked with a reason
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7ab46bb2de
commit
809c67a54a
21 files changed
+153
-7
No files matched your search
@@ -31,6 +31,14 @@ export const zh: Record<string, string> = {
|
||||
"Writing": "写作方式",
|
||||
"superseded": "已被取代",
|
||||
"stalled": "已停滞",
|
||||
"blocked": "已阻塞",
|
||||
"Block": "阻塞",
|
||||
"Unblock": "解除阻塞",
|
||||
"Block task": "阻塞任务",
|
||||
"Why is it blocked?": "为什么阻塞?",
|
||||
"Send back": "退回",
|
||||
"Reopen": "重新打开",
|
||||
"Back to backlog": "退回待办",
|
||||
"needs you": "待你处理",
|
||||
"waiting": "等待中",
|
||||
"Resumes on its own at {time}.": "将于 {time} 自动恢复。",
|
||||
|
||||
@@ -177,6 +177,12 @@ export function WorkPage({ snapshot, actions, navigate, focus }: PageProps) {
|
||||
onAdvance={advance}
|
||||
onRun={runTask}
|
||||
onOpenTask={setDetailTaskId}
|
||||
onMove={async (task, status) => {
|
||||
await actions.run(() => window.agentCompany.updateTaskStatus({ orgId, taskId: task.id, status }));
|
||||
}}
|
||||
onBlock={async (task, reason) => {
|
||||
await actions.run(() => window.agentCompany.setTaskBlocked({ orgId, taskId: task.id, reason }));
|
||||
}}
|
||||
onScope={(next) => {
|
||||
setScope(next);
|
||||
setDetailTaskId(null);
|
||||
@@ -357,7 +363,10 @@ function TaskCard({
|
||||
<div className="row" style={{ flexWrap: "wrap", gap: 6, minWidth: 0 }}>
|
||||
{project ? <Pill>{project.name}</Pill> : <Pill>{capitalize(task.risk)}</Pill>}
|
||||
{task.stalledReason && task.status !== "done" && <Pill tone="red">{t("stalled")}</Pill>}
|
||||
{task.hold && !task.stalledReason && (
|
||||
{task.blockedReason && task.status !== "done" && (
|
||||
<span title={task.blockedReason} style={{ display: "inline-flex" }}><Pill tone="red">{t("blocked")}</Pill></span>
|
||||
)}
|
||||
{task.hold && !task.stalledReason && !task.blockedReason && (
|
||||
<span title={task.hold.reason} style={{ display: "inline-flex" }}>
|
||||
<Pill tone={task.hold.waitingOn === "operator" ? "red" : "amber"}>{task.hold.waitingOn === "operator" ? t("needs you") : t("waiting")}</Pill>
|
||||
</span>
|
||||
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
TaskRunRecord,
|
||||
WorkspaceRecord,
|
||||
} from "../../../../shared/domain";
|
||||
import { TASK_STATUS, type WritingMode } from "../../../../shared/domain";
|
||||
import { TASK_STATUS, type TaskStatus, type WritingMode } from "../../../../shared/domain";
|
||||
import { auditEventLabel, formatRelative } from "../types";
|
||||
import { WRITING_OPTIONS, descendantIds, nextStatus, prettyRunState, prettyStatus, providerFor, runBlocker, runsForTask, runTone } from "./task-helpers";
|
||||
import type { SnapshotActions } from "../../app/useSnapshot";
|
||||
@@ -43,6 +43,8 @@ export function TaskDetailDialog({
|
||||
onAdvance,
|
||||
onRun,
|
||||
onOpenTask,
|
||||
onMove,
|
||||
onBlock,
|
||||
onScope,
|
||||
navigate,
|
||||
}: {
|
||||
@@ -62,10 +64,20 @@ export function TaskDetailDialog({
|
||||
onAdvance: (task: TaskRecord) => void;
|
||||
onRun: (task: TaskRecord) => void;
|
||||
onOpenTask: (taskId: string) => void;
|
||||
/** Moves the task to any status, backwards included: send back, reopen, back to backlog. */
|
||||
onMove: (task: TaskRecord, status: TaskStatus) => Promise<void>;
|
||||
/** Blocks the task with a reason, or unblocks it with null. */
|
||||
onBlock: (task: TaskRecord, reason: string | null) => Promise<void>;
|
||||
onScope: (scope: Focus) => void;
|
||||
navigate: PageProps["navigate"];
|
||||
}) {
|
||||
const [tab, setTab] = useState<Tab>("overview");
|
||||
const [blocking, setBlocking] = useState(false);
|
||||
const [actionError, setActionError] = useState<string | null>(null);
|
||||
const act = (operation: () => Promise<void>, after?: () => void) => {
|
||||
setActionError(null);
|
||||
operation().then(after, (error: unknown) => setActionError(error instanceof Error ? error.message : String(error)));
|
||||
};
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [editError, setEditError] = useState<string | null>(null);
|
||||
|
||||
@@ -148,8 +160,30 @@ export function TaskDetailDialog({
|
||||
<ActivityTab audit={taskAudit} approvals={taskApprovals} onOpenApproval={(approvalId) => navigate("approvals", { kind: "approval", id: approvalId })} />
|
||||
)}
|
||||
|
||||
{blocking && (
|
||||
<form
|
||||
className="row"
|
||||
style={{ gap: 8, marginTop: 12 }}
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
const reason = String(new FormData(event.currentTarget).get("reason") ?? "");
|
||||
act(() => onBlock(task, reason), () => setBlocking(false));
|
||||
}}
|
||||
>
|
||||
<input className="input" name="reason" aria-label={t("Why is it blocked?")} placeholder={t("Why is it blocked?")} required autoFocus style={{ flex: 1 }} />
|
||||
<Button tone="primary" size="small" type="submit">{t("Block task")}</Button>
|
||||
<Button tone="ghost" size="small" onClick={() => setBlocking(false)}>{t("Cancel")}</Button>
|
||||
</form>
|
||||
)}
|
||||
{actionError && <div className="form-error" role="alert" style={{ marginTop: 8 }}>{actionError}</div>}
|
||||
<div className="modal-actions">
|
||||
<Button tone="ghost" onClick={deleteTask}><Icon name="x" size={16} /> {t("Delete")}</Button>
|
||||
{task.blockedReason
|
||||
? <Button onClick={() => act(() => onBlock(task, null))}>{t("Unblock")}</Button>
|
||||
: task.status !== TASK_STATUS.DONE && !blocking && <Button onClick={() => setBlocking(true)}>{t("Block")}</Button>}
|
||||
{task.status === TASK_STATUS.REVIEW && <Button onClick={() => act(() => onMove(task, TASK_STATUS.IN_PROGRESS))}>{t("Send back")}</Button>}
|
||||
{task.status === TASK_STATUS.DONE && <Button onClick={() => act(() => onMove(task, TASK_STATUS.IN_PROGRESS))}>{t("Reopen")}</Button>}
|
||||
{task.status === TASK_STATUS.IN_PROGRESS && <Button onClick={() => act(() => onMove(task, TASK_STATUS.BACKLOG))}>{t("Back to backlog")}</Button>}
|
||||
{assignee && <Button onClick={() => onRun(task)} disabled={runBlocker(task, provider, taskRuns) !== null}><Icon name="send" size={16} /> {t("Run")}</Button>}
|
||||
{advanceTo && <Button tone="primary" onClick={() => onAdvance(task)}><Icon name="check" size={16} /> {prettyStatus(advanceTo)}</Button>}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user