Q151: deleting a task first shows what goes with it, then waits for the result
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
809c67a54a
commit
18f1e1fcc3
13 files changed
+171
-5
No files matched your search
@@ -39,6 +39,15 @@ export const zh: Record<string, string> = {
|
||||
"Send back": "退回",
|
||||
"Reopen": "重新打开",
|
||||
"Back to backlog": "退回待办",
|
||||
"Delete this task?": "删除这个任务?",
|
||||
"Delete task": "删除任务",
|
||||
"Nothing else is affected.": "不影响其他内容。",
|
||||
"{n} sessions and their action records are deleted.": "{n} 次会话及其操作记录将被删除。",
|
||||
"{n} subtasks stay and move to the top level.": "{n} 个子任务保留,并移到顶层。",
|
||||
"{n} documents stay, no longer linked to this task.": "{n} 份文档保留,不再关联此任务。",
|
||||
"{n} open reviews of it are closed.": "{n} 个进行中的审查将被关闭。",
|
||||
"{n} pending approvals for it stay pending.": "{n} 个待处理审批仍保持待处理。",
|
||||
"A session is running; stop it or wait for it before deleting.": "有会话正在运行;请先停止或等待其结束再删除。",
|
||||
"needs you": "待你处理",
|
||||
"waiting": "等待中",
|
||||
"Resumes on its own at {time}.": "将于 {time} 自动恢复。",
|
||||
|
||||
@@ -161,6 +161,7 @@ export function WorkPage({ snapshot, actions, navigate, focus }: PageProps) {
|
||||
<ProjectDialog open={newProjectOpen} orgId={orgId} goals={goals} agents={agents} actions={actions} onClose={() => setNewProjectOpen(false)} />
|
||||
<NewTaskDialog open={newTaskOpen} orgId={orgId} snapshot={snapshot} actions={actions} onClose={() => setNewTaskOpen(false)} />
|
||||
<TaskDetailDialog
|
||||
key={detailTask?.id ?? "none"}
|
||||
task={detailTask}
|
||||
agents={agents}
|
||||
projects={projects}
|
||||
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
TaskRunRecord,
|
||||
WorkspaceRecord,
|
||||
} from "../../../../shared/domain";
|
||||
import { TASK_STATUS, type TaskStatus, type WritingMode } from "../../../../shared/domain";
|
||||
import { TASK_STATUS, type TaskDeleteImpact, 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";
|
||||
@@ -73,6 +73,8 @@ export function TaskDetailDialog({
|
||||
}) {
|
||||
const [tab, setTab] = useState<Tab>("overview");
|
||||
const [blocking, setBlocking] = useState(false);
|
||||
// The impact is kept with the task it was counted for, and the delete removes exactly that task.
|
||||
const [deleteImpact, setDeleteImpact] = useState<{ taskId: string; impact: TaskDeleteImpact } | null>(null);
|
||||
const [actionError, setActionError] = useState<string | null>(null);
|
||||
const act = (operation: () => Promise<void>, after?: () => void) => {
|
||||
setActionError(null);
|
||||
@@ -115,10 +117,17 @@ export function TaskDetailDialog({
|
||||
})).then((r) => { if (r) setEditing(false); }, (error: unknown) => setEditError(error instanceof Error ? error.message : String(error)));
|
||||
};
|
||||
|
||||
const deleteTask = () => {
|
||||
void actions.run(() => window.agentCompany.deleteTask({ orgId, taskId: task.id }));
|
||||
onClose();
|
||||
const askDelete = () => {
|
||||
const taskId = task.id;
|
||||
setActionError(null);
|
||||
window.agentCompany.taskDeleteImpact({ orgId, taskId }).then((result) => {
|
||||
if (!result.ok) throw new Error(result.error.message);
|
||||
setDeleteImpact({ taskId, impact: result.data });
|
||||
}).catch((error: unknown) => setActionError(error instanceof Error ? error.message : String(error)));
|
||||
};
|
||||
const confirmDelete = (taskId: string) => act(async () => {
|
||||
await actions.run(() => window.agentCompany.deleteTask({ orgId, taskId }));
|
||||
}, onClose);
|
||||
|
||||
return (
|
||||
<div className="modal-backdrop" role="presentation" onMouseDown={onClose}>
|
||||
@@ -175,9 +184,12 @@ export function TaskDetailDialog({
|
||||
<Button tone="ghost" size="small" onClick={() => setBlocking(false)}>{t("Cancel")}</Button>
|
||||
</form>
|
||||
)}
|
||||
{deleteImpact && deleteImpact.taskId === task.id && (
|
||||
<DeleteConfirm impact={deleteImpact.impact} onConfirm={() => confirmDelete(deleteImpact.taskId)} onCancel={() => setDeleteImpact(null)} />
|
||||
)}
|
||||
{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>
|
||||
<Button tone="ghost" onClick={askDelete} disabled={deleteImpact !== null}><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>}
|
||||
@@ -382,3 +394,26 @@ function MetaLabel({ children }: { children: string }) {
|
||||
function LinkButton({ label, onClick, children }: { label: string; onClick: () => void; children: string }) {
|
||||
return <button type="button" className="link-button" aria-label={label} onClick={onClick} style={{ textAlign: "left" }}>{children}</button>;
|
||||
}
|
||||
|
||||
/** What a delete will do, said before it happens; a running session stops it. */
|
||||
function DeleteConfirm({ impact, onConfirm, onCancel }: { impact: TaskDeleteImpact; onConfirm: () => void; onCancel: () => void }) {
|
||||
const lines = [
|
||||
impact.runs > 0 && t("{n} sessions and their action records are deleted.", { n: impact.runs }),
|
||||
impact.subtasks > 0 && t("{n} subtasks stay and move to the top level.", { n: impact.subtasks }),
|
||||
impact.documents > 0 && t("{n} documents stay, no longer linked to this task.", { n: impact.documents }),
|
||||
impact.openReviews > 0 && t("{n} open reviews of it are closed.", { n: impact.openReviews }),
|
||||
impact.pendingApprovals > 0 && t("{n} pending approvals for it stay pending.", { n: impact.pendingApprovals }),
|
||||
].filter((line): line is string => typeof line === "string");
|
||||
return (
|
||||
<div role="alertdialog" aria-label={t("Delete this task?")} style={{ marginTop: 12, padding: 12, border: "1px solid var(--red)", borderRadius: 10 }}>
|
||||
<div style={{ fontWeight: 600, marginBottom: 6 }}>{t("Delete this task?")}</div>
|
||||
{lines.length === 0 && <div style={{ fontSize: 13, color: "var(--text-2)" }}>{t("Nothing else is affected.")}</div>}
|
||||
{lines.map((line) => <div key={line} style={{ fontSize: 13, color: "var(--text-2)" }}>{line}</div>)}
|
||||
{impact.running > 0 && <div className="form-error" role="alert">{t("A session is running; stop it or wait for it before deleting.")}</div>}
|
||||
<div className="row" style={{ gap: 8, marginTop: 10 }}>
|
||||
<Button tone="primary" size="small" onClick={onConfirm} disabled={impact.running > 0}>{t("Delete task")}</Button>
|
||||
<Button tone="ghost" size="small" onClick={onCancel}>{t("Cancel")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user