Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | 10x 55x 21x 9x 12x 34x 16x 18x 11x 11x 7x 4x 3x 3x | import type { JSX } from "react";
import type { TaskData } from "../../hooks/types.js";
import styles from "./TaskActionButtons.module.scss";
/**
* Normalized session statuses for which Resume is meaningful. A task can be
* `paused` either because its session is alive but idle (awaiting input) or
* because its session is genuinely suspended/stopped; only the latter is
* resumable, so Resume is gated on these statuses.
*/
const RESUMABLE_SESSION_STATUSES: ReadonlySet<string> = new Set(["stopped", "suspended"]);
/** Props for {@link TaskActionButtons}. */
export interface TaskActionButtonsProps {
/** The task whose status drives which buttons are shown. */
task: TaskData;
/** Active session id — required to enable the Pause button. */
sessionId: string | undefined;
/**
* Status of the task's latest session (already normalized by
* `mapSessionStatus`: `idle` / `running` / `stopped` / `suspended` / ...).
* Gates the Resume button — Resume only makes sense when the session is
* actually suspended/stopped, not while it is alive and awaiting input.
*/
latestSessionStatus: string | undefined;
/** Whether the task is blocked by incomplete dependencies. */
isBlocked: boolean;
/** Start (or retry) the task. */
onStart: () => void;
/** Resume a paused task. */
onResume: () => void;
/** Stop the task. */
onStop: () => void;
/** Pause the task by killing its session. */
onPause: () => void;
/** Request task deletion (typically opens a confirm dialog). */
onDelete: () => void;
/** Open the task editor. */
onEdit: () => void;
}
/**
* Renders status-appropriate action buttons for a task.
*
* Which buttons appear depends on `task.status` and `isBlocked`.
* Returns `undefined` for unrecognized statuses.
*/
export function TaskActionButtons({
task,
sessionId,
latestSessionStatus,
isBlocked,
onStart,
onResume,
onStop,
onPause,
onDelete,
onEdit,
}: TaskActionButtonsProps): JSX.Element | undefined {
if (task.status === "not_started") {
if (isBlocked) {
return (
<div className={styles.actionButtons} data-testid="task-action-buttons">
<button onClick={onEdit} className={styles.btnGhost} data-testid="task-action-edit">
Edit
</button>
<button onClick={onDelete} className={styles.btnDanger} data-testid="task-action-delete">
Delete
</button>
</div>
);
}
return (
<div className={styles.actionButtons} data-testid="task-action-buttons">
<button data-testid="task-header-start" onClick={onStart} className={styles.btnPrimary}>
Start
</button>
<button onClick={onEdit} className={styles.btnGhost} data-testid="task-action-edit">
Edit
</button>
<button onClick={onDelete} className={styles.btnDanger} data-testid="task-action-delete">
Delete
</button>
</div>
);
}
if (task.status === "working") {
return (
<div className={styles.actionButtons} data-testid="task-action-buttons">
<button onClick={onStop} className={styles.btnDanger} data-testid="task-action-stop">
Stop
</button>
<button
onClick={onPause}
disabled={!sessionId}
className={styles.btnGhost}
data-testid="task-action-pause"
>
Pause
</button>
</div>
);
}
if (task.status === "paused") {
const resumable =
latestSessionStatus !== undefined && RESUMABLE_SESSION_STATUSES.has(latestSessionStatus);
return (
<div className={styles.actionButtons} data-testid="task-action-buttons">
<button onClick={onStop} className={styles.btnPrimary} data-testid="task-action-stop">
Stop
</button>
{resumable && (
<button onClick={onResume} className={styles.btnGhost} data-testid="task-action-resume">
Resume
</button>
)}
<button onClick={onDelete} className={styles.btnDanger} data-testid="task-action-delete">
Delete
</button>
</div>
);
}
if (task.status === "complete") {
return (
<div className={styles.actionButtons} data-testid="task-action-buttons">
<button onClick={onDelete} className={styles.btnDanger} data-testid="task-action-delete">
Delete
</button>
</div>
);
}
if (task.status === "failed") {
return (
<div className={styles.actionButtons} data-testid="task-action-buttons">
<button onClick={onStart} className={styles.btnPrimary} data-testid="task-header-start">
Retry
</button>
<button onClick={onDelete} className={styles.btnDanger} data-testid="task-action-delete">
Delete
</button>
</div>
);
}
return undefined;
}
|