summaryrefslogblamecommitdiff
path: root/makima/frontend/src/components/mesh/InlineSubtaskEditor.tsx
blob: 3621b08477913fb2df4c6eda3717f5728658e772 (plain) (tree)





































































































































































































































































                                                                                                                                                                                                  
import { useState, useCallback, useEffect } from "react";
import type { TaskWithSubtasks, TaskStatus } from "../../lib/api";
import { getTask, updateTask } from "../../lib/api";

interface InlineSubtaskEditorProps {
  subtaskId: string;
  onClose: () => void;
  onUpdated: () => void;
  onNavigate?: (taskId: string) => void;
}

function getStatusColor(status: TaskStatus): string {
  switch (status) {
    case "pending":
      return "text-[#9bc3ff]";
    case "running":
      return "text-green-400";
    case "paused":
      return "text-yellow-400";
    case "blocked":
      return "text-orange-400";
    case "done":
      return "text-emerald-400";
    case "failed":
      return "text-red-400";
    case "merged":
      return "text-purple-400";
    default:
      return "text-[#9bc3ff]";
  }
}

function getStatusBgColor(status: TaskStatus): string {
  switch (status) {
    case "pending":
      return "bg-[rgba(117,170,252,0.1)]";
    case "running":
      return "bg-green-400/10";
    case "paused":
      return "bg-yellow-400/10";
    case "blocked":
      return "bg-orange-400/10";
    case "done":
      return "bg-emerald-400/10";
    case "failed":
      return "bg-red-400/10";
    case "merged":
      return "bg-purple-400/10";
    default:
      return "bg-[rgba(117,170,252,0.1)]";
  }
}

export function InlineSubtaskEditor({
  subtaskId,
  onClose,
  onUpdated,
  onNavigate,
}: InlineSubtaskEditorProps) {
  const [subtask, setSubtask] = useState<TaskWithSubtasks | null>(null);
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [isEditing, setIsEditing] = useState(false);
  const [editName, setEditName] = useState("");
  const [editDescription, setEditDescription] = useState("");
  const [editPlan, setEditPlan] = useState("");

  // Load subtask details
  useEffect(() => {
    setLoading(true);
    getTask(subtaskId)
      .then((task) => {
        setSubtask(task);
        setEditName(task.name);
        setEditDescription(task.description || "");
        setEditPlan(task.plan);
      })
      .catch((err) => {
        console.error("Failed to load subtask:", err);
      })
      .finally(() => {
        setLoading(false);
      });
  }, [subtaskId]);

  const handleSave = useCallback(async () => {
    if (!subtask || saving) return;
    setSaving(true);
    try {
      await updateTask(subtaskId, {
        name: editName,
        description: editDescription || undefined,
        plan: editPlan,
        version: subtask.version,
      });
      // Refresh subtask
      const updated = await getTask(subtaskId);
      setSubtask(updated);
      setIsEditing(false);
      onUpdated();
    } catch (err) {
      console.error("Failed to save subtask:", err);
    } finally {
      setSaving(false);
    }
  }, [subtask, subtaskId, editName, editDescription, editPlan, saving, onUpdated]);

  const handleCancel = useCallback(() => {
    if (subtask) {
      setEditName(subtask.name);
      setEditDescription(subtask.description || "");
      setEditPlan(subtask.plan);
    }
    setIsEditing(false);
  }, [subtask]);

  if (loading) {
    return (
      <div className="p-4 bg-[rgba(0,0,0,0.2)] border-l-2 border-[#3f6fb3]">
        <div className="font-mono text-xs text-[#75aafc]">Loading subtask...</div>
      </div>
    );
  }

  if (!subtask) {
    return (
      <div className="p-4 bg-[rgba(0,0,0,0.2)] border-l-2 border-red-400">
        <div className="font-mono text-xs text-red-400">Failed to load subtask</div>
      </div>
    );
  }

  return (
    <div className="bg-[rgba(0,0,0,0.2)] border-l-2 border-[#3f6fb3]">
      {/* Header */}
      <div className="flex items-center justify-between p-3 border-b border-[rgba(117,170,252,0.15)]">
        <div className="flex items-center gap-2">
          <button
            onClick={onClose}
            className="font-mono text-[10px] text-[#555] hover:text-[#75aafc]"
          >
            [close]
          </button>
          <span
            className={`px-1.5 py-0.5 font-mono text-[9px] uppercase ${getStatusColor(
              subtask.status as TaskStatus
            )} ${getStatusBgColor(subtask.status as TaskStatus)} border border-current/20`}
          >
            {subtask.status}
          </span>
          {onNavigate && (
            <button
              onClick={() => onNavigate(subtaskId)}
              className="font-mono text-[10px] text-[#75aafc] hover:text-[#9bc3ff]"
            >
              [open full view]
            </button>
          )}
        </div>
        <div className="flex items-center gap-2">
          {isEditing ? (
            <>
              <button
                onClick={handleCancel}
                disabled={saving}
                className="px-2 py-0.5 font-mono text-[10px] text-[#555] hover:text-[#9bc3ff] disabled:opacity-50"
              >
                Cancel
              </button>
              <button
                onClick={handleSave}
                disabled={saving}
                className="px-2 py-0.5 font-mono text-[10px] text-green-400 border border-green-400/30 hover:border-green-400/50 disabled:opacity-50"
              >
                {saving ? "..." : "Save"}
              </button>
            </>
          ) : (
            <button
              onClick={() => setIsEditing(true)}
              className="px-2 py-0.5 font-mono text-[10px] text-[#75aafc] hover:text-[#9bc3ff]"
            >
              Edit
            </button>
          )}
        </div>
      </div>

      {/* Content */}
      <div className="p-3 space-y-3">
        {/* Name */}
        {isEditing ? (
          <input
            type="text"
            value={editName}
            onChange={(e) => setEditName(e.target.value)}
            className="w-full bg-transparent border border-[rgba(117,170,252,0.25)] text-[#dbe7ff] font-mono text-sm px-2 py-1 outline-none focus:border-[#3f6fb3]"
            placeholder="Subtask name"
          />
        ) : (
          <div className="font-mono text-sm text-[#dbe7ff]">{subtask.name}</div>
        )}

        {/* Description */}
        {isEditing ? (
          <textarea
            value={editDescription}
            onChange={(e) => setEditDescription(e.target.value)}
            className="w-full bg-transparent border border-[rgba(117,170,252,0.25)] text-[#9bc3ff] font-mono text-xs px-2 py-1 outline-none focus:border-[#3f6fb3] min-h-[40px] resize-y"
            placeholder="Description (optional)"
          />
        ) : subtask.description ? (
          <div className="font-mono text-xs text-[#75aafc]">{subtask.description}</div>
        ) : null}

        {/* Plan */}
        <div className="space-y-1">
          <div className="font-mono text-[10px] text-[#555] uppercase">Plan</div>
          {isEditing ? (
            <textarea
              value={editPlan}
              onChange={(e) => setEditPlan(e.target.value)}
              className="w-full bg-[rgba(0,0,0,0.2)] border border-[rgba(117,170,252,0.25)] text-[#dbe7ff] font-mono text-xs px-2 py-1 outline-none focus:border-[#3f6fb3] min-h-[100px] resize-y"
              placeholder="Plan/instructions..."
            />
          ) : (
            <pre className="bg-[rgba(0,0,0,0.2)] border border-[rgba(117,170,252,0.15)] p-2 font-mono text-xs text-[#9bc3ff] whitespace-pre-wrap max-h-[150px] overflow-y-auto">
              {subtask.plan}
            </pre>
          )}
        </div>

        {/* Progress/Error */}
        {subtask.progressSummary && (
          <div className="font-mono text-[10px] text-[#75aafc]">
            <span className="text-[#555]">Progress:</span> {subtask.progressSummary}
          </div>
        )}
        {subtask.errorMessage && (
          <div className="font-mono text-[10px] text-red-400">
            <span className="text-red-400/50">Error:</span> {subtask.errorMessage}
          </div>
        )}

        {/* Nested subtasks indicator */}
        {subtask.subtasks.length > 0 && (
          <div className="font-mono text-[10px] text-[#555]">
            Has {subtask.subtasks.length} subtask{subtask.subtasks.length > 1 ? "s" : ""}
            {onNavigate && (
              <button
                onClick={() => onNavigate(subtaskId)}
                className="ml-2 text-[#75aafc] hover:text-[#9bc3ff]"
              >
                [view all]
              </button>
            )}
          </div>
        )}
      </div>
    </div>
  );
}