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

337 lines
12 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 SidebarItem {
id: string;
name: string;
icon: string;
color?: string;
category: string;
}
interface SidebarCategory {
id: string;
name: string;
icon: string;
items: SidebarItem[];
}
const sidebarCategories: SidebarCategory[] = [
{
id: "projects",
name: "Projects",
icon: "🎯",
items: [
{ id: "sitemente", name: "SiteMente", icon: "🌐", color: "#ff7bc0", category: "projects" },
{ id: "holacompi", name: "HolaCompi", icon: "🤝", color: "#6366f1", category: "projects" },
],
},
{
id: "tasks",
name: "Tasks",
icon: "✓",
items: [
{ id: "all", name: "All Tasks", icon: "📋", category: "tasks" },
],
},
{
id: "chat",
name: "Chat",
icon: "💬",
items: [
{ id: "voice", name: "Voice Chat", icon: "🎤", category: "chat" },
],
},
{
id: "council",
name: "Council",
icon: "🏛️",
items: [
{ id: "ai-settings", name: "AI Settings", icon: "🤖", category: "council" },
],
},
{
id: "calendar",
name: "Calendar",
icon: "📅",
items: [
{ id: "brief", name: "Morning Brief", icon: "☀️", category: "calendar" },
],
},
{
id: "memory",
name: "Memory",
icon: "🧠",
items: [
{ id: "logs", name: "Session Logs", icon: "📝", category: "memory" },
],
},
];
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" },
};
export default function MissionControlDashboard() {
const { tasks, toggleTask, updateTaskStatus, getProjectProgress, getTasksByProject } = useMissionControl();
const [selectedItem, setSelectedItem] = useState<string>("sitemente");
const [filter, setFilter] = useState<TaskStatus | "all">("all");
const [expandedCategories, setExpandedCategories] = useState<string[]>(["projects"]);
// Find selected item
let selectedCategory = sidebarCategories.find(c => c.items.some(i => i.id === selectedItem));
let currentItem = selectedCategory?.items.find(i => i.id === selectedItem);
const projectTasks = currentItem?.category === "projects" ? getTasksByProject(selectedItem as any) : tasks;
const filteredTasks = filter === "all" ? projectTasks : projectTasks.filter((t) => t.status === filter);
const progress = currentItem?.category === "projects" ? getProjectProgress(selectedItem as any) : 0;
const toggleCategory = (catId: string) => {
setExpandedCategories(prev =>
prev.includes(catId) ? prev.filter(id => id !== catId) : [...prev, catId]
);
};
// Render content based on selection
const renderContent = () => {
const category = currentItem?.category;
if (category === "chat") {
return (
<div className="rounded-xl border border-white/10 bg-white/5 p-6">
<VoiceChat />
</div>
);
}
if (category === "council") {
return (
<div className="rounded-xl border border-white/10 bg-white/5 p-6">
<AIManagement />
</div>
);
}
if (category === "calendar") {
return (
<div className="space-y-4">
<div className="rounded-xl border border-white/10 bg-white/5 p-6">
<h3 className="text-lg font-semibold mb-4">📅 Morning Brief</h3>
<p className="text-white/60 mb-4">Daily intelligence at 6am CET</p>
<a href="/morning-brief" className="inline-flex items-center gap-2 px-4 py-2 bg-brand-pink rounded-lg text-sm font-medium hover:bg-[#ff7bc0] transition">
Open Calendar
</a>
</div>
<VoiceChat />
</div>
);
}
if (category === "memory") {
return (
<div className="rounded-xl border border-white/10 bg-white/5 p-6">
<h3 className="text-lg font-semibold mb-4">🧠 Memory & Logs</h3>
<p className="text-white/60 mb-4">Session history and daily logs</p>
<div className="space-y-2">
<p className="text-sm text-white/70">Memory is stored in:</p>
<ul className="text-sm text-white/50 space-y-1">
<li> localStorage (browser)</li>
<li> GitHub repo (daily commits)</li>
<li> MEMORY.md (curated)</li>
</ul>
</div>
</div>
);
}
// Default: Tasks view
return renderTasksView();
};
const renderTasksView = () => (
<>
{/* 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">{currentItem?.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-[500px] 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>
</>
);
return (
<div className="min-h-screen bg-[#1a1625] text-white flex">
{/* Left Sidebar */}
<aside className="w-64 border-r border-white/10 bg-[#1a1625] p-4">
{/* Logo */}
<div className="flex items-center gap-3 mb-6 pb-4 border-b border-white/10">
<div className="w-10 h-10 rounded-xl bg-brand-pink flex items-center justify-center text-xl">👁</div>
<div>
<h1 className="font-bold">Mission Control</h1>
<p className="text-xs text-white/50">Horus AI Assistant</p>
</div>
</div>
{/* Categories */}
<nav className="space-y-1">
{sidebarCategories.map((category) => (
<div key={category.id}>
<button
onClick={() => toggleCategory(category.id)}
className="w-full flex items-center justify-between px-3 py-2 rounded-lg text-sm font-medium text-white/70 hover:bg-white/5 hover:text-white transition"
>
<span className="flex items-center gap-2">
<span>{category.icon}</span>
<span>{category.name}</span>
</span>
<span className={`transition ${expandedCategories.includes(category.id) ? "rotate-90" : ""}`}></span>
</button>
{expandedCategories.includes(category.id) && (
<div className="ml-4 mt-1 space-y-1">
{category.items.map((item) => (
<button
key={item.id}
onClick={() => setSelectedItem(item.id)}
className={`w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition ${
selectedItem === item.id
? "bg-white/10 text-white border border-white/20"
: "text-white/60 hover:bg-white/5 hover:text-white"
}`}
>
<span>{item.icon}</span>
<span>{item.name}</span>
</button>
))}
</div>
)}
</div>
))}
</nav>
{/* Quick Links */}
<div className="mt-6 pt-4 border-t border-white/10">
<p className="text-xs text-white/40 uppercase mb-2 px-3">Quick</p>
<a href="/" className="flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-white/60 hover:bg-white/5 hover:text-white transition">
🏠 SiteMente Site
</a>
</div>
</aside>
{/* Main Content */}
<main className="flex-1 p-6 overflow-y-auto">
{/* Header */}
<header className="flex items-center justify-between mb-6 pb-4 border-b border-white/10">
<div>
<h2 className="text-xl font-bold flex items-center gap-2">
<span>{currentItem?.icon}</span>
<span>{currentItem?.name}</span>
</h2>
<p className="text-sm text-white/50">
{currentItem?.category === "projects" ? `${progress}% complete` : `${tasks.length} total tasks`}
</p>
</div>
{currentItem?.category === "projects" && (
<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>
)}
</header>
{/* Content */}
{renderContent()}
</main>
</div>
);
}