summaryrefslogblamecommitdiff
path: root/makima/frontend/src/components/mesh/TaskList.tsx
blob: a37e564a6253e0aff9ae0392ba9f89555ecc6e16 (plain) (tree)



































































































































































                                                                                                                                                                                            
import type { TaskSummary, TaskStatus } from "../../lib/api";

interface TaskListProps {
  tasks: TaskSummary[];
  loading: boolean;
  onSelect: (id: string) => void;
  onDelete: (id: string) => void;
  onCreate: () => void;
}

function formatDate(dateStr: string): string {
  const date = new Date(dateStr);
  return date.toLocaleDateString("en-US", {
    month: "short",
    day: "numeric",
    year: "numeric",
    hour: "2-digit",
    minute: "2-digit",
  });
}

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 TaskList({
  tasks,
  loading,
  onSelect,
  onDelete,
  onCreate,
}: TaskListProps) {
  if (loading) {
    return (
      <div className="panel h-full flex items-center justify-center">
        <div className="font-mono text-[#9bc3ff] text-sm">Loading tasks...</div>
      </div>
    );
  }

  // Separate root tasks (no parent) from subtasks
  const rootTasks = tasks.filter((t) => !t.parentTaskId);

  return (
    <div className="panel h-full flex flex-col">
      <div className="flex items-center justify-between p-4 pb-2 border-b border-dashed border-[rgba(117,170,252,0.35)]">
        <div className="font-mono text-xs text-[#9bc3ff] tracking-wide uppercase">
          MESH//TASKS
        </div>
        <button
          onClick={onCreate}
          className="px-3 py-1 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] hover:bg-[rgba(117,170,252,0.05)] transition-colors uppercase"
        >
          + New Task
        </button>
      </div>

      <div className="flex-1 overflow-y-auto">
        {rootTasks.length === 0 ? (
          <div className="text-center text-[#9bc3ff] text-sm font-mono opacity-60 py-8">
            No tasks yet. Create one to start orchestrating Claude Code instances.
          </div>
        ) : (
          <div className="divide-y divide-[rgba(117,170,252,0.15)]">
            {rootTasks.map((task) => (
              <div
                key={task.id}
                className="p-4 hover:bg-[rgba(117,170,252,0.05)] transition-colors"
              >
                <div className="flex items-start justify-between gap-4">
                  <button
                    onClick={() => onSelect(task.id)}
                    className="flex-1 text-left"
                  >
                    <div className="flex items-center gap-2 mb-1">
                      <h3 className="font-mono text-sm text-[#dbe7ff]">
                        {task.name}
                      </h3>
                      <span
                        className={`px-2 py-0.5 font-mono text-[10px] uppercase ${getStatusColor(
                          task.status
                        )} ${getStatusBgColor(task.status)} border border-current/20`}
                      >
                        {task.status}
                      </span>
                      {task.depth === 0 && task.subtaskCount > 0 && (
                        <span className="px-2 py-0.5 font-mono text-[10px] text-purple-400 bg-purple-400/10 border border-purple-400/20">
                          Orchestrator
                        </span>
                      )}
                      {task.priority > 0 && (
                        <span className="px-2 py-0.5 font-mono text-[10px] text-orange-400 bg-orange-400/10 border border-orange-400/20">
                          P{task.priority}
                        </span>
                      )}
                    </div>
                    {task.progressSummary && (
                      <p className="font-mono text-xs text-[#9bc3ff] mb-2 line-clamp-2">
                        {task.progressSummary}
                      </p>
                    )}
                    <div className="flex gap-4 font-mono text-[10px] text-[#75aafc]">
                      {task.subtaskCount > 0 && (
                        <span>{task.subtaskCount} subtasks</span>
                      )}
                      <span>{formatDate(task.createdAt)}</span>
                    </div>
                  </button>
                  <button
                    onClick={(e) => {
                      e.stopPropagation();
                      onDelete(task.id);
                    }}
                    className="px-2 py-1 font-mono text-[10px] text-red-400 hover:bg-red-400/10 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase"
                  >
                    Delete
                  </button>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}