Files
sitemente/components/mission-control/MissionControlDashboard.tsx
T

332 lines
13 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
import { useMissionControl } from "@/lib/mission-control/store";
import { TaskStatus } from "@/lib/mission-control/types";
import VoiceChat from "./VoiceChat";
import AIManagement from "@/components/ai-management/AIManagement";
interface ProjectSummary {
id: string;
name: string;
description: string;
status: "active" | "paused" | "completed";
color: string;
icon: string;
}
const projects: ProjectSummary[] = [
{
id: "sitemente",
name: "SiteMente",
description: "AI website platform for local businesses (B2B)",
status: "active",
color: "#ff7bc0",
icon: "🌐",
},
{
id: "holacompi",
name: "HolaCompi",
description: "AI ally for immigrants/consumers (B2C)",
status: "paused",
color: "#6366f1",
icon: "🤝",
},
{
id: "infrastructure",
name: "Infrastructure",
description: "Security, backups, APIs, and system config",
status: "active",
color: "#10b981",
icon: "🔒",
},
{
id: "horus",
name: "Horus AI",
description: "Manage my skills, APIs, and automation",
status: "active",
color: "#f59e0b",
icon: "🤖",
},
];
const statusConfig: Record<TaskStatus, { label: string; color: string }> = {
todo: { label: "To Do", color: "text-white/70" },
in_progress: { label: "In Progress", color: "text-yellow-400" },
done: { label: "Done", color: "text-green-400" },
blocked: { label: "Blocked", color: "text-red-400" },
paused: { label: "Paused", color: "text-gray-400" },
};
type ViewType = "tasks" | "horus";
export default function MissionControlDashboard() {
const { tasks, toggleTask, updateTaskStatus, getProjectProgress, getTasksByProject } =
useMissionControl();
const [selectedProject, setSelectedProject] = useState<string>("sitemente");
const [filter, setFilter] = useState<TaskStatus | "all">("all");
const [view, setView] = useState<ViewType>("tasks");
const projectTasks = getTasksByProject(selectedProject as any);
const filteredTasks = filter === "all" ? projectTasks : projectTasks.filter((t) => t.status === filter);
const progress = getProjectProgress(selectedProject as any);
const selectedProjectData = projects.find((p) => p.id === selectedProject)!;
// If viewing Horus AI management
if (view === "horus") {
return (
<div className="min-h-screen bg-[#1a1625] text-white">
{/* Header */}
<header className="border-b border-white/10 bg-[#2d2640] px-6 py-4">
<div className="mx-auto flex max-w-7xl items-center justify-between">
<div className="flex items-center gap-4">
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-brand-pink text-xl">
👁
</div>
<div>
<h1 className="text-xl font-bold">Mission Control</h1>
<p className="text-xs text-white/60">Horus AI Management</p>
</div>
</div>
<nav className="flex items-center gap-2">
<button
onClick={() => setView("tasks")}
className="px-3 py-1.5 rounded-lg text-sm font-medium text-white/70 hover:bg-white/10 transition"
>
Back
</button>
</nav>
</div>
</header>
<main className="mx-auto max-w-4xl px-6 py-8">
<AIManagement />
{/* Voice Chat */}
<div className="mt-8">
<VoiceChat />
</div>
</main>
</div>
);
}
return (
<div className="min-h-screen bg-[#1a1625] text-white">
{/* Header */}
<header className="border-b border-white/10 bg-[#2d2640] px-6 py-4">
<div className="mx-auto flex max-w-7xl items-center justify-between">
<div className="flex items-center gap-4">
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-brand-pink text-xl">
👁
</div>
<div>
<h1 className="text-xl font-bold">Mission Control</h1>
<p className="text-xs text-white/60">SiteMente + HolaCompi + Infrastructure</p>
</div>
</div>
<div className="flex items-center gap-4">
<div className="text-right">
<p className="text-sm text-white/60">Total Progress</p>
<p className="text-2xl font-bold">{progress}%</p>
</div>
<div className="h-12 w-12 rounded-full border-4 border-brand-pink bg-white/10">
<svg className="h-full w-full -rotate-90" viewBox="0 0 36 36">
<circle cx="18" cy="18" r="16" fill="none" stroke="currentColor" strokeWidth="3" className="text-white/20" />
<circle cx="18" cy="18" r="16" fill="none" stroke="currentColor" strokeWidth="3" strokeDasharray={`${progress}, 100`} className="text-brand-pink" />
</svg>
</div>
</div>
</div>
</header>
<div className="mx-auto max-w-7xl px-6 py-8">
<div className="flex gap-8">
{/* Left Sidebar */}
<aside className="w-64 flex-shrink-0">
<nav className="space-y-2">
{projects.map((project) => {
const p = getProjectProgress(project.id as any);
const isSelected = selectedProject === project.id;
const isHorus = project.id === "horus";
return (
<button
key={project.id}
onClick={() => {
if (isHorus) {
setView("horus");
} else {
setSelectedProject(project.id);
setView("tasks");
}
}}
className={`w-full flex items-center gap-3 p-3 rounded-xl text-left transition ${
isSelected && !isHorus
? "bg-white/10 border border-white/20"
: "hover:bg-white/5"
}`}
>
<span className="text-xl">{project.icon}</span>
<div className="flex-1 min-w-0">
<p className="font-medium text-sm truncate">{project.name}</p>
<div className="mt-1 h-1 w-full rounded-full bg-white/10">
<div
className="h-full rounded-full transition-all"
style={{ width: `${p}%`, backgroundColor: project.color }}
/>
</div>
</div>
<span className="text-xs text-white/50">{p}%</span>
</button>
);
})}
</nav>
{/* Quick Links */}
<div className="mt-8 pt-6 border-t border-white/10">
<p className="text-xs text-white/50 uppercase mb-3">Quick Links</p>
<div className="space-y-2">
<a href="/morning-brief" className="flex items-center gap-2 p-2 rounded-lg text-sm text-white/70 hover:bg-white/5 hover:text-white transition">
Morning Brief
</a>
<a href="/" className="flex items-center gap-2 p-2 rounded-lg text-sm text-white/70 hover:bg-white/5 hover:text-white transition">
🏠 SiteMente Site
</a>
</div>
</div>
</aside>
{/* Main Content */}
<main className="flex-1">
{/* Project Tabs */}
<div className="mb-6 flex gap-2 overflow-x-auto pb-2">
{projects.filter(p => p.id !== "horus").map((project) => {
const p = getProjectProgress(project.id as any);
return (
<button
key={project.id}
onClick={() => setSelectedProject(project.id)}
className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium whitespace-nowrap transition ${
selectedProject === project.id
? "bg-white/10 border border-white/20"
: "hover:bg-white/5"
}`}
>
<span>{project.icon}</span>
<span>{project.name}</span>
<span className={`px-1.5 py-0.5 rounded text-xs ${
project.status === "active" ? "bg-green-500/20 text-green-400" : "bg-yellow-500/20 text-yellow-400"
}`}>
{project.status}
</span>
</button>
);
})}
</div>
{/* Stats Row */}
<div className="mb-6 grid grid-cols-4 gap-3">
{(["todo", "in_progress", "done", "blocked"] as TaskStatus[]).map((status) => {
const count = projectTasks.filter((t) => t.status === status).length;
const config = statusConfig[status];
return (
<button
key={status}
onClick={() => setFilter(filter === status ? "all" : status)}
className={`rounded-xl border p-3 text-center transition ${
filter === status
? "border-white/40 bg-white/10"
: "border-white/10 bg-white/5 hover:border-white/20"
}`}
>
<p className={`text-xl font-bold ${config.color}`}>{count}</p>
<p className="text-xs text-white/50">{config.label}</p>
</button>
);
})}
</div>
{/* Task List */}
<div className="rounded-xl border border-white/10 bg-white/5">
<div className="border-b border-white/10 px-4 py-3">
<div className="flex items-center justify-between">
<h2 className="font-semibold">{selectedProjectData?.name} Tasks</h2>
<p className="text-sm text-white/50">
{filteredTasks.filter((t) => t.status === "done").length} / {filteredTasks.length} done
</p>
</div>
</div>
<div className="divide-y divide-white/5 max-h-[400px] overflow-y-auto">
{filteredTasks.map((task) => {
const config = statusConfig[task.status];
return (
<div
key={task.id}
className={`flex items-center gap-3 px-4 py-3 transition hover:bg-white/5 ${
task.status === "done" ? "opacity-50" : ""
}`}
>
<button
onClick={() => toggleTask(task.id)}
className={`flex-shrink-0 w-5 h-5 rounded-full border-2 flex items-center justify-center transition ${
task.status === "done"
? "border-green-500 bg-green-500 text-white"
: "border-white/30 hover:border-white/50"
}`}
>
{task.status === "done" && (
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
</svg>
)}
</button>
<div className="flex-1 min-w-0">
<p className={`text-sm font-medium truncate ${task.status === "done" ? "line-through text-white/50" : ""}`}>
{task.title}
</p>
</div>
{task.priority === "critical" && (
<span className="px-2 py-0.5 rounded-full bg-red-500/20 text-red-400 text-xs">
CRITICAL
</span>
)}
<select
value={task.status}
onChange={(e) => updateTaskStatus(task.id, e.target.value as TaskStatus)}
className="text-xs bg-transparent border border-white/20 rounded px-2 py-1"
>
<option value="todo">To Do</option>
<option value="in_progress">In Progress</option>
<option value="done">Done</option>
<option value="blocked">Blocked</option>
</select>
</div>
);
})}
</div>
{filteredTasks.length === 0 && (
<div className="px-4 py-8 text-center text-white/50 text-sm">
No tasks match the current filter.
</div>
)}
</div>
{/* Voice Chat */}
<div className="mt-6">
<VoiceChat />
</div>
</main>
</div>
</div>
</div>
);
}