summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/contracts/ContractDetail.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/components/contracts/ContractDetail.tsx')
-rw-r--r--makima/frontend/src/components/contracts/ContractDetail.tsx810
1 files changed, 0 insertions, 810 deletions
diff --git a/makima/frontend/src/components/contracts/ContractDetail.tsx b/makima/frontend/src/components/contracts/ContractDetail.tsx
deleted file mode 100644
index 02c129e..0000000
--- a/makima/frontend/src/components/contracts/ContractDetail.tsx
+++ /dev/null
@@ -1,810 +0,0 @@
-import { useState, useEffect, useCallback } from "react";
-import type {
- ContractWithRelations,
- ContractPhase,
- ContractStatus,
- ContractRepository,
- FileSummary,
- TaskSummary,
- TemplateSummary,
-} from "../../lib/api";
-import {
- listTemplates,
- getTemplate,
- createFile,
-} from "../../lib/api";
-import { PhaseProgressBar } from "./PhaseProgressBar";
-import { PhaseHint } from "./PhaseHint";
-import { RepositoryPanel } from "./RepositoryPanel";
-import { ContractCliInput } from "./ContractCliInput";
-import { PhaseDeliverablesPanel } from "./PhaseDeliverablesPanel";
-import { CommandModePanel } from "./CommandModePanel";
-import { TaskTree } from "../mesh/TaskTree";
-
-type Tab = "overview" | "repos" | "files" | "tasks";
-
-interface ContractDetailProps {
- contract: ContractWithRelations;
- loading: boolean;
- onBack: () => void;
- onUpdate: (name: string, description: string) => void;
- onDelete: () => void;
- onPhaseChange: (phase: ContractPhase) => void;
- onStatusChange: (status: ContractStatus) => void;
- onFileSelect: (id: string) => void;
- onTaskSelect: (id: string) => void;
- onTaskCreate: (name: string, plan: string, repositoryUrl?: string) => void;
- onRefresh: () => void;
- // Repository callbacks
- onAddRemoteRepo: (name: string, url: string, isPrimary: boolean) => void;
- onAddLocalRepo: (name: string, path: string, isPrimary: boolean) => void;
- onCreateManagedRepo: (name: string, isPrimary: boolean) => void;
- onDeleteRepo: (repoId: string) => void;
- onSetRepoPrimary: (repoId: string) => void;
- // File creation callback for phase deliverables
- onCreateFileFromTemplate?: (templateId: string, suggestedName: string) => void;
-}
-
-const statusConfig: Record<ContractStatus, { label: string; color: string }> = {
- active: { label: "Active", color: "text-green-400" },
- completed: { label: "Completed", color: "text-blue-400" },
- archived: { label: "Archived", color: "text-[#555]" },
-};
-
-export function ContractDetail({
- contract,
- loading,
- onBack,
- onUpdate,
- onDelete,
- onPhaseChange,
- onStatusChange,
- onFileSelect,
- onTaskSelect,
- onTaskCreate,
- onRefresh,
- onAddRemoteRepo,
- onAddLocalRepo,
- onCreateManagedRepo,
- onDeleteRepo,
- onSetRepoPrimary,
- onCreateFileFromTemplate,
-}: ContractDetailProps) {
- const [activeTab, setActiveTab] = useState<Tab>("overview");
- const [isEditing, setIsEditing] = useState(false);
- const [name, setName] = useState(contract.name);
- const [description, setDescription] = useState(contract.description || "");
-
- const handleSave = () => {
- onUpdate(name, description);
- setIsEditing(false);
- };
-
- const handleCancel = () => {
- setName(contract.name);
- setDescription(contract.description || "");
- setIsEditing(false);
- };
-
- if (loading) {
- return (
- <div className="panel h-full flex items-center justify-center">
- <div className="font-mono text-[#9bc3ff] text-sm">Loading...</div>
- </div>
- );
- }
-
- const tabs: { key: Tab; label: string; count?: number }[] = [
- { key: "overview", label: "Overview" },
- { key: "repos", label: "Repositories", count: contract.repositories.length },
- { key: "files", label: "Files", count: contract.files.length },
- { key: "tasks", label: "Tasks", count: contract.tasks.length },
- ];
-
- return (
- <div className="panel h-full flex flex-col min-h-0">
- {/* Header */}
- <div className="p-4 border-b border-dashed border-[rgba(117,170,252,0.35)]">
- <div className="flex items-center justify-between mb-3">
- <button
- onClick={onBack}
- className="font-mono text-xs text-[#75aafc] hover:text-[#9bc3ff] transition-colors"
- >
- &larr; Back to list
- </button>
- <div className="flex items-center gap-2">
- {isEditing ? (
- <>
- <button
- onClick={handleCancel}
- className="px-3 py-1.5 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors uppercase"
- >
- Cancel
- </button>
- <button
- onClick={handleSave}
- className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors uppercase"
- >
- Save
- </button>
- </>
- ) : (
- <>
- <button
- onClick={() => setIsEditing(true)}
- className="px-3 py-1.5 font-mono text-xs text-[#dbe7ff] border border-[#0f3c78] hover:border-[#3f6fb3] transition-colors uppercase"
- >
- Edit
- </button>
- <button
- onClick={onDelete}
- className="px-3 py-1.5 font-mono text-xs text-red-400 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase"
- >
- Delete
- </button>
- </>
- )}
- </div>
- </div>
-
- {isEditing ? (
- <div className="space-y-3">
- <input
- type="text"
- value={name}
- onChange={(e) => setName(e.target.value)}
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
- placeholder="Contract name"
- />
- <textarea
- value={description}
- onChange={(e) => setDescription(e.target.value)}
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc] resize-none"
- rows={2}
- placeholder="Description (optional)"
- />
- </div>
- ) : (
- <>
- <div className="flex items-center gap-3 mb-2">
- <h2 className="font-mono text-lg text-[#dbe7ff]">
- {contract.name}
- </h2>
- <span
- className={`font-mono text-xs uppercase ${
- statusConfig[contract.status].color
- }`}
- >
- {statusConfig[contract.status].label}
- </span>
- {contract.localOnly && (
- <span className="px-2 py-0.5 font-mono text-[10px] uppercase text-amber-400 border border-amber-400/30 bg-amber-400/10">
- Local-Only
- </span>
- )}
- </div>
- {contract.description && (
- <p className="font-mono text-sm text-[#9bc3ff] mb-3">
- {contract.description}
- </p>
- )}
- </>
- )}
-
- {/* Phase progress */}
- <div className="mt-4 pt-4 border-t border-dashed border-[rgba(117,170,252,0.2)]">
- <PhaseProgressBar
- currentPhase={contract.phase}
- contractType={contract.contractType}
- onPhaseClick={onPhaseChange}
- />
- </div>
- </div>
-
- {/* Tabs */}
- <div className="flex border-b border-[rgba(117,170,252,0.2)]">
- {tabs.map((tab) => (
- <button
- key={tab.key}
- onClick={() => setActiveTab(tab.key)}
- className={`
- px-4 py-2 font-mono text-xs uppercase tracking-wider transition-colors
- ${
- activeTab === tab.key
- ? "text-[#dbe7ff] border-b-2 border-[#75aafc]"
- : "text-[#555] hover:text-[#9bc3ff]"
- }
- `}
- >
- {tab.label}
- {tab.count !== undefined && tab.count > 0 && (
- <span className="ml-1 text-[10px]">({tab.count})</span>
- )}
- </button>
- ))}
- </div>
-
- {/* Tab content */}
- <div className="flex-1 overflow-y-auto p-4 min-h-0">
- {activeTab === "overview" && (
- <OverviewTab
- contract={contract}
- onStatusChange={onStatusChange}
- onPhaseChange={onPhaseChange}
- onCreateFile={onCreateFileFromTemplate}
- onRefresh={onRefresh}
- />
- )}
-
- {activeTab === "repos" && (
- <RepositoryPanel
- repositories={contract.repositories}
- onAddRemote={onAddRemoteRepo}
- onAddLocal={onAddLocalRepo}
- onCreateManaged={onCreateManagedRepo}
- onDelete={onDeleteRepo}
- onSetPrimary={onSetRepoPrimary}
- />
- )}
-
- {activeTab === "files" && (
- <FilesTab
- files={contract.files}
- contractId={contract.id}
- contractPhase={contract.phase}
- onSelect={onFileSelect}
- onRefresh={onRefresh}
- />
- )}
-
- {activeTab === "tasks" && (
- <TasksTab
- tasks={contract.tasks}
- repositories={contract.repositories}
- supervisorTaskId={contract.supervisorTaskId}
- contractType={contract.contractType}
- onSelect={onTaskSelect}
- onCreate={onTaskCreate}
- />
- )}
- </div>
-
- {/* Chat Input */}
- <ContractCliInput
- contractId={contract.id}
- contract={contract}
- onUpdate={onRefresh}
- />
- </div>
- );
-}
-
-// Overview tab
-function OverviewTab({
- contract,
- onStatusChange,
- onPhaseChange,
- onCreateFile,
- onRefresh,
-}: {
- contract: ContractWithRelations;
- onStatusChange: (status: ContractStatus) => void;
- onPhaseChange: (phase: ContractPhase) => void;
- onCreateFile?: (templateId: string, suggestedName: string) => void;
- onRefresh: () => void;
-}) {
- return (
- <div className="space-y-6">
- {/* Command Mode controls */}
- <CommandModePanel contract={contract} onUpdate={onRefresh} />
-
- {/* Phase deliverables checklist */}
- <PhaseDeliverablesPanel
- contract={contract}
- onCreateFile={onCreateFile}
- />
-
- {/* Phase hint */}
- <PhaseHint contract={contract} onAdvancePhase={onPhaseChange} />
-
- {/* Task progress summary */}
- <TaskStatusSummary tasks={contract.tasks} />
-
- {/* Stats */}
- <div className="grid grid-cols-3 gap-4">
- <StatCard label="Repositories" value={contract.repositories.length} />
- <StatCard label="Files" value={contract.files.length} />
- <StatCard label="Tasks" value={contract.tasks.length} />
- </div>
-
- {/* Status change */}
- <div>
- <h3 className="font-mono text-xs text-[#75aafc] uppercase mb-2">
- Status
- </h3>
- <div className="flex gap-2">
- {(["active", "completed", "archived"] as ContractStatus[]).map(
- (status) => (
- <button
- key={status}
- onClick={() => onStatusChange(status)}
- className={`
- px-3 py-1.5 font-mono text-xs uppercase transition-colors
- ${
- contract.status === status
- ? "bg-[rgba(117,170,252,0.1)] text-[#9bc3ff] border border-[rgba(117,170,252,0.3)]"
- : "text-[#555] border border-transparent hover:text-[#75aafc]"
- }
- `}
- >
- {status}
- </button>
- )
- )}
- </div>
- </div>
-
- {/* Metadata */}
- <div>
- <h3 className="font-mono text-xs text-[#75aafc] uppercase mb-2">
- Details
- </h3>
- <div className="space-y-1 font-mono text-xs text-[#555]">
- <p>Created: {new Date(contract.createdAt).toLocaleString()}</p>
- <p>Updated: {new Date(contract.updatedAt).toLocaleString()}</p>
- <p>Version: {contract.version}</p>
- </div>
- </div>
- </div>
- );
-}
-
-function StatCard({ label, value }: { label: string; value: number }) {
- return (
- <div className="p-3 border border-[rgba(117,170,252,0.2)]">
- <div className="font-mono text-2xl text-[#dbe7ff]">{value}</div>
- <div className="font-mono text-[10px] text-[#555] uppercase">{label}</div>
- </div>
- );
-}
-
-// Task status summary with progress bar
-function TaskStatusSummary({ tasks }: { tasks: TaskSummary[] }) {
- if (tasks.length === 0) return null;
-
- // Count tasks by status
- const statusCounts = {
- done: 0,
- merged: 0,
- running: 0,
- pending: 0,
- failed: 0,
- other: 0,
- };
-
- for (const task of tasks) {
- switch (task.status) {
- case "done":
- statusCounts.done++;
- break;
- case "merged":
- statusCounts.merged++;
- break;
- case "running":
- case "initializing":
- case "starting":
- statusCounts.running++;
- break;
- case "pending":
- statusCounts.pending++;
- break;
- case "failed":
- statusCounts.failed++;
- break;
- default:
- statusCounts.other++;
- }
- }
-
- const completedCount = statusCounts.done + statusCounts.merged;
- const progressPercent = (completedCount / tasks.length) * 100;
-
- // Build summary parts
- const parts: string[] = [];
- if (completedCount > 0) parts.push(`${completedCount} done`);
- if (statusCounts.running > 0) parts.push(`${statusCounts.running} running`);
- if (statusCounts.pending > 0) parts.push(`${statusCounts.pending} pending`);
- if (statusCounts.failed > 0) parts.push(`${statusCounts.failed} failed`);
-
- return (
- <div className="space-y-2">
- <h3 className="font-mono text-xs text-[#75aafc] uppercase">
- Task Progress
- </h3>
-
- {/* Progress bar */}
- <div className="h-2 bg-[rgba(117,170,252,0.1)] rounded overflow-hidden">
- <div
- className="h-full bg-green-400 transition-all duration-300"
- style={{ width: `${progressPercent}%` }}
- />
- </div>
-
- {/* Summary text */}
- <div className="flex items-center justify-between">
- <span className="font-mono text-xs text-[#9bc3ff]">
- {parts.join(", ")}
- </span>
- <span className="font-mono text-xs text-[#555]">
- {completedCount}/{tasks.length} completed
- </span>
- </div>
- </div>
- );
-}
-
-// Phase color mapping for badges
-const phaseColors: Record<ContractPhase, string> = {
- research: "bg-purple-500/20 text-purple-400 border-purple-400/30",
- specify: "bg-blue-500/20 text-blue-400 border-blue-400/30",
- plan: "bg-cyan-500/20 text-cyan-400 border-cyan-400/30",
- execute: "bg-green-500/20 text-green-400 border-green-400/30",
- review: "bg-yellow-500/20 text-yellow-400 border-yellow-400/30",
-};
-
-// Files tab with template creation
-function FilesTab({
- files,
- contractId,
- contractPhase,
- onSelect,
- onRefresh,
-}: {
- files: FileSummary[];
- contractId: string;
- contractPhase: ContractPhase;
- onSelect: (id: string) => void;
- onRefresh: () => void;
-}) {
- const [showTemplateModal, setShowTemplateModal] = useState(false);
- const [templates, setTemplates] = useState<TemplateSummary[]>([]);
- const [loadingTemplates, setLoadingTemplates] = useState(false);
- const [creating, setCreating] = useState(false);
- const [fileName, setFileName] = useState("");
- const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(null);
-
- // Load templates when modal opens
- useEffect(() => {
- if (showTemplateModal) {
- setLoadingTemplates(true);
- listTemplates(contractPhase)
- .then((res) => setTemplates(res.templates))
- .catch((err) => console.error("Failed to load templates:", err))
- .finally(() => setLoadingTemplates(false));
- }
- }, [showTemplateModal, contractPhase]);
-
- const handleCreateFromTemplate = useCallback(async () => {
- if (!fileName.trim() || !selectedTemplateId) return;
-
- setCreating(true);
- try {
- // Get the full template with body
- const template = await getTemplate(selectedTemplateId);
-
- // Create the file with contract (files must belong to contracts)
- await createFile({
- contractId,
- name: fileName.trim(),
- description: template.description,
- body: template.suggestedBody,
- });
-
- // Reset and close
- setShowTemplateModal(false);
- setFileName("");
- setSelectedTemplateId(null);
- onRefresh();
- } catch (err) {
- console.error("Failed to create file from template:", err);
- } finally {
- setCreating(false);
- }
- }, [fileName, selectedTemplateId, contractId, onRefresh]);
-
- const handleCloseModal = () => {
- setShowTemplateModal(false);
- setFileName("");
- setSelectedTemplateId(null);
- };
-
- return (
- <div className="space-y-4">
- {/* Create from template button */}
- <button
- onClick={() => setShowTemplateModal(true)}
- className="px-3 py-1.5 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors"
- >
- + Create from Template
- </button>
-
- {/* Template Selection Modal */}
- {showTemplateModal && (
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
- <div className="w-full max-w-lg p-6 bg-[#0a1628] border border-[rgba(117,170,252,0.3)] max-h-[80vh] flex flex-col">
- <div className="flex items-center justify-between mb-4">
- <h3 className="font-mono text-sm text-[#75aafc] uppercase">
- Create File from Template
- </h3>
- <span className={`px-2 py-0.5 text-[10px] font-mono uppercase border rounded ${phaseColors[contractPhase]}`}>
- {contractPhase} phase
- </span>
- </div>
-
- <div className="space-y-4 flex-1 overflow-y-auto">
- {/* File name input */}
- <div>
- <label className="block font-mono text-xs text-[#555] uppercase mb-1">
- File Name
- </label>
- <input
- type="text"
- value={fileName}
- onChange={(e) => setFileName(e.target.value)}
- placeholder="e.g., Project Requirements"
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
- autoFocus
- />
- </div>
-
- {/* Template selection */}
- <div>
- <label className="block font-mono text-xs text-[#555] uppercase mb-2">
- Select Template
- </label>
- {loadingTemplates ? (
- <p className="font-mono text-xs text-[#555]">Loading templates...</p>
- ) : templates.length === 0 ? (
- <p className="font-mono text-xs text-[#555]">No templates available for {contractPhase} phase</p>
- ) : (
- <div className="space-y-2 max-h-60 overflow-y-auto">
- {templates.map((template) => (
- <button
- key={template.id}
- onClick={() => setSelectedTemplateId(template.id)}
- className={`w-full text-left p-3 border transition-colors ${
- selectedTemplateId === template.id
- ? "border-[#75aafc] bg-[rgba(117,170,252,0.1)]"
- : "border-[rgba(117,170,252,0.2)] hover:border-[rgba(117,170,252,0.4)]"
- }`}
- >
- <div className="flex items-center justify-between mb-1">
- <span className="font-mono text-sm text-[#dbe7ff]">
- {template.name}
- </span>
- <span className="font-mono text-[10px] text-[#555]">
- {template.elementCount} elements
- </span>
- </div>
- <p className="font-mono text-xs text-[#555]">
- {template.description}
- </p>
- </button>
- ))}
- </div>
- )}
- </div>
- </div>
-
- <div className="flex gap-2 justify-end mt-4 pt-4 border-t border-[rgba(117,170,252,0.2)]">
- <button
- onClick={handleCloseModal}
- className="px-4 py-2 font-mono text-xs text-[#9bc3ff] hover:text-[#dbe7ff] transition-colors"
- >
- Cancel
- </button>
- <button
- onClick={handleCreateFromTemplate}
- disabled={!fileName.trim() || !selectedTemplateId || creating}
- className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
- >
- {creating ? "Creating..." : "Create File"}
- </button>
- </div>
- </div>
- </div>
- )}
-
- {/* File list */}
- {files.length === 0 ? (
- <p className="font-mono text-xs text-[#555]">
- No files in this contract. Create one from a template above.
- </p>
- ) : (
- <div className="space-y-2">
- {files.map((file) => (
- <button
- key={file.id}
- onClick={() => onSelect(file.id)}
- className="w-full text-left p-3 border border-[rgba(117,170,252,0.2)] hover:border-[rgba(117,170,252,0.4)] transition-colors"
- >
- <div className="flex items-center justify-between">
- <div className="flex items-center gap-2">
- <span className="font-mono text-sm text-[#dbe7ff]">
- {file.name}
- </span>
- {file.contractPhase && (
- <span
- className={`px-1.5 py-0.5 text-[9px] font-mono uppercase border rounded ${
- phaseColors[file.contractPhase]
- }`}
- title={`Added during ${file.contractPhase} phase`}
- >
- {file.contractPhase}
- </span>
- )}
- </div>
- <span className="font-mono text-[10px] text-[#555]">
- v{file.version}
- </span>
- </div>
- {file.description && (
- <p className="font-mono text-xs text-[#555] mt-1 truncate">
- {file.description}
- </p>
- )}
- </button>
- ))}
- </div>
- )}
- </div>
- );
-}
-
-// Tasks tab - now using TaskTree for supervisor view
-function TasksTab({
- tasks,
- repositories,
- supervisorTaskId,
- contractType,
- onSelect,
- onCreate,
-}: {
- tasks: TaskSummary[];
- repositories: ContractRepository[];
- supervisorTaskId: string | null;
- contractType: string;
- onSelect: (id: string) => void;
- onCreate: (name: string, plan: string, repositoryUrl?: string) => void;
-}) {
- const [isCreating, setIsCreating] = useState(false);
- const [taskName, setTaskName] = useState("");
- const [taskPlan, setTaskPlan] = useState("# Plan\n\nDescribe what this task should accomplish...");
-
- // Find primary repository or first ready one
- const readyRepos = repositories.filter((r) => r.status === "ready");
- const primaryRepo = readyRepos.find((r) => r.isPrimary) || readyRepos[0];
- const [selectedRepoId, setSelectedRepoId] = useState<string>(primaryRepo?.id || "");
-
- const handleCreate = () => {
- if (!taskName.trim()) return;
- const selectedRepo = repositories.find((r) => r.id === selectedRepoId);
- // Get the URL - for remote repos it's repositoryUrl, for local it's the local path
- const repoUrl = selectedRepo?.repositoryUrl || selectedRepo?.localPath;
- onCreate(taskName.trim(), taskPlan, repoUrl || undefined);
- setIsCreating(false);
- setTaskName("");
- setTaskPlan("# Plan\n\nDescribe what this task should accomplish...");
- setSelectedRepoId(primaryRepo?.id || "");
- };
-
- const handleCancel = () => {
- setIsCreating(false);
- setTaskName("");
- setTaskPlan("# Plan\n\nDescribe what this task should accomplish...");
- setSelectedRepoId(primaryRepo?.id || "");
- };
-
- return (
- <div className="space-y-4">
- {/* TaskTree with supervisor view */}
- <TaskTree
- tasks={tasks}
- supervisorTaskId={supervisorTaskId}
- onSelect={onSelect}
- />
-
- {/* Manual task creation - show for task-type contracts or contracts without supervisors */}
- {(contractType === "task" || !supervisorTaskId) && (
- <>
- <div className="border-t border-[rgba(117,170,252,0.2)] pt-4">
- <button
- onClick={() => setIsCreating(true)}
- className="px-3 py-1.5 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] transition-colors"
- >
- + Create Task Manually
- </button>
- </div>
-
- {/* Create Task Modal */}
- {isCreating && (
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
- <div className="w-full max-w-lg p-6 bg-[#0a1628] border border-[rgba(117,170,252,0.3)]">
- <h3 className="font-mono text-sm text-[#75aafc] uppercase mb-4">
- Create Task
- </h3>
- <div className="space-y-4">
- <div>
- <label className="block font-mono text-xs text-[#555] uppercase mb-1">
- Name
- </label>
- <input
- type="text"
- value={taskName}
- onChange={(e) => setTaskName(e.target.value)}
- placeholder="Task name"
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
- autoFocus
- />
- </div>
-
- {/* Repository selection */}
- {readyRepos.length > 0 && (
- <div>
- <label className="block font-mono text-xs text-[#555] uppercase mb-1">
- Repository
- </label>
- <select
- value={selectedRepoId}
- onChange={(e) => setSelectedRepoId(e.target.value)}
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc]"
- >
- <option value="">No repository</option>
- {readyRepos.map((repo) => (
- <option key={repo.id} value={repo.id}>
- {repo.name}
- {repo.isPrimary ? " (Primary)" : ""}
- {" - "}
- {repo.sourceType}
- </option>
- ))}
- </select>
- </div>
- )}
-
- <div>
- <label className="block font-mono text-xs text-[#555] uppercase mb-1">
- Plan
- </label>
- <textarea
- value={taskPlan}
- onChange={(e) => setTaskPlan(e.target.value)}
- rows={6}
- className="w-full px-3 py-2 bg-[#0d1b2d] border border-[#3f6fb3] text-[#dbe7ff] font-mono text-sm focus:outline-none focus:border-[#75aafc] resize-none"
- />
- </div>
-
- <div className="flex gap-2 justify-end">
- <button
- onClick={handleCancel}
- className="px-4 py-2 font-mono text-xs text-[#9bc3ff] hover:text-[#dbe7ff] transition-colors"
- >
- Cancel
- </button>
- <button
- onClick={handleCreate}
- disabled={!taskName.trim()}
- className="px-4 py-2 font-mono text-xs text-[#dbe7ff] bg-[#0f3c78] border border-[#3f6fb3] hover:bg-[#153667] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
- >
- Create
- </button>
- </div>
- </div>
- </div>
- </div>
- )}
- </>
- )}
- </div>
- );
-}