| 1 | import { useEffect, useRef, useState } from "react"; |
| 2 | import { useI18n } from "../lib/i18n"; |
| 3 | import type { GoalLifecycleView } from "../lib/types"; |
| 4 | |
| 5 | export function GoalLifecycleActions({ |
| 6 | goalView, |
| 7 | goalStatus, |
| 8 | disabled, |
| 9 | running, |
| 10 | onEditGoal, |
| 11 | onPauseGoal, |
| 12 | onResumeGoal, |
| 13 | onStopGoal, |
| 14 | }: { |
| 15 | goalView?: GoalLifecycleView; |
| 16 | goalStatus?: string; |
| 17 | disabled?: boolean; |
| 18 | running: boolean; |
| 19 | onEditGoal: (objective: string, maxGoalRounds: number | null) => void; |
| 20 | onPauseGoal: () => void; |
| 21 | onResumeGoal: () => void; |
| 22 | onStopGoal: () => void; |
| 23 | }) { |
| 24 | const { t } = useI18n(); |
| 25 | const resumable = goalView?.phase === "paused" |
| 26 | || goalView?.phase === "blocked" |
| 27 | || (goalView?.phase === "active" && goalView.activation === "disarmed") |
| 28 | || (!goalView && goalStatus === "blocked"); |
| 29 | |
| 30 | const [draft, setDraft] = useState<{ objective: string; rounds: string; invalid: boolean } | null>(null); |
| 31 | |
| 32 | const editButtonRef = useRef<HTMLButtonElement>(null); |
| 33 | const wasOpen = useRef(false); |
| 34 | useEffect(() => { |
| 35 | if (wasOpen.current && !draft) editButtonRef.current?.focus(); |
| 36 | wasOpen.current = draft !== null; |
| 37 | }, [draft]); |
| 38 | |
| 39 | const openEditor = () => { |
| 40 | if (!goalView) return; |
| 41 | setDraft({ objective: goalView.objective, rounds: goalView.maxGoalRounds?.toString() ?? "", invalid: false }); |
| 42 | }; |
| 43 | |
| 44 | const submitEdit = (event: React.FormEvent) => { |
| 45 | event.preventDefault(); |
| 46 | if (!draft) return; |
| 47 | const trimmed = draft.rounds.trim(); |
| 48 | const limit = trimmed === "" ? null : Number(trimmed); |
| 49 | if (limit !== null && (!Number.isSafeInteger(limit) || limit <= 0)) { |
| 50 | setDraft({ ...draft, invalid: true }); |
| 51 | return; |
| 52 | } |
| 53 | setDraft(null); |
| 54 | onEditGoal(draft.objective, limit); |
| 55 | }; |
| 56 | |
| 57 | return <> |
| 58 | {draft && ( |
| 59 | <form className="goal-edit-form" data-goal-edit-form="" onSubmit={submitEdit} onKeyDown={(e) => { if (e.key === "Escape") { e.preventDefault(); e.stopPropagation(); setDraft(null); } }}> |
| 60 | <label> |
| 61 | {t("composer.goalEditObjective")} |
| 62 | <textarea |
| 63 | className="remote-secret-dialog__input" |
| 64 | rows={2} |
| 65 | data-goal-edit-objective="" |
| 66 | autoFocus |
| 67 | value={draft.objective} |
| 68 | onChange={(e) => setDraft({ ...draft, objective: e.target.value })} |
| 69 | /> |
| 70 | </label> |
| 71 | <label> |
| 72 | {t("composer.goalEditMaxRounds")} |
| 73 | <input |
| 74 | className="remote-secret-dialog__input" |
| 75 | data-goal-edit-rounds="" |
| 76 | inputMode="numeric" |
| 77 | value={draft.rounds} |
| 78 | onChange={(e) => setDraft({ ...draft, rounds: e.target.value, invalid: false })} |
| 79 | /> |
| 80 | </label> |
| 81 | {draft.invalid && <p className="goal-edit-form__error" role="alert" data-goal-edit-error="">{t("composer.goalEditInvalidRounds")}</p>} |
| 82 | <div className="goal-edit-form__actions"> |
| 83 | <button type="button" className="btn" onClick={() => setDraft(null)}>{t("common.cancel")}</button> |
| 84 | <button type="submit" className="btn btn--primary" disabled={draft.objective.trim() === ""}>{t("common.save")}</button> |
| 85 | </div> |
| 86 | </form> |
| 87 | )} |
| 88 | {goalView && <button ref={editButtonRef} type="button" className="composer-intent-menu__stop" onClick={openEditor} disabled={disabled || draft !== null}> |
| 89 | {t("composer.taskModeEditGoal")} |
| 90 | </button>} |
| 91 | {resumable ? ( |
| 92 | <button type="button" className="composer-intent-menu__stop" onClick={onResumeGoal} disabled={disabled}> |
| 93 | {t("composer.taskModeResumeGoal")} |
| 94 | </button> |
| 95 | ) : ( |
| 96 | <button type="button" className="composer-intent-menu__stop" onClick={onPauseGoal} disabled={disabled}> |
| 97 | {t("composer.taskModePauseGoal")} |
| 98 | </button> |
| 99 | )} |
| 100 | <button type="button" className="composer-intent-menu__stop" onClick={onStopGoal} disabled={disabled || running}> |
| 101 | {t("composer.taskModeStopGoal")} |
| 102 | </button> |
| 103 | </>; |
| 104 | } |
| 105 |