feat: Add search, quick add task, export, today's focus, keyboard shortcuts
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useMissionControl } from "@/lib/mission-control/store";
|
||||
import { TaskStatus } from "@/lib/mission-control/types";
|
||||
import VoiceChat from "./VoiceChat";
|
||||
@@ -22,55 +22,25 @@ interface SidebarCategory {
|
||||
}
|
||||
|
||||
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" },
|
||||
],
|
||||
},
|
||||
{ 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 }> = {
|
||||
@@ -82,11 +52,37 @@ const statusConfig: Record<TaskStatus, { label: string; color: string }> = {
|
||||
};
|
||||
|
||||
export default function MissionControlDashboard() {
|
||||
const { tasks, toggleTask, updateTaskStatus, getProjectProgress, getTasksByProject } = useMissionControl();
|
||||
|
||||
const { tasks, toggleTask, updateTaskStatus, addTask, getProjectProgress, getTasksByProject } = useMissionControl();
|
||||
const [selectedItem, setSelectedItem] = useState<string>("sitemente");
|
||||
const [filter, setFilter] = useState<TaskStatus | "all">("all");
|
||||
const [expandedCategories, setExpandedCategories] = useState<string[]>(["projects"]);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [showAddTask, setShowAddTask] = useState(false);
|
||||
const [newTaskTitle, setNewTaskTitle] = useState("");
|
||||
const [newTaskProject, setNewTaskProject] = useState("sitemente");
|
||||
|
||||
// Keyboard shortcuts
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "/" && !e.ctrlKey && !e.metaKey) {
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.tagName !== "INPUT" && target.tagName !== "TEXTAREA") {
|
||||
e.preventDefault();
|
||||
document.getElementById("search-input")?.focus();
|
||||
}
|
||||
}
|
||||
if (e.key === "n" && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
setShowAddTask(true);
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
setShowAddTask(false);
|
||||
setSearchQuery("");
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, []);
|
||||
|
||||
// Find selected item
|
||||
let selectedCategory = sidebarCategories.find(c => c.items.some(i => i.id === selectedItem));
|
||||
@@ -94,255 +90,196 @@ export default function MissionControlDashboard() {
|
||||
|
||||
const projectTasks = currentItem?.category === "projects" ? getTasksByProject(selectedItem as any) : tasks;
|
||||
const filteredTasks = filter === "all" ? projectTasks : projectTasks.filter((t) => t.status === filter);
|
||||
const searchedTasks = searchQuery
|
||||
? filteredTasks.filter(t => t.title.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
: filteredTasks;
|
||||
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]
|
||||
);
|
||||
setExpandedCategories(prev => prev.includes(catId) ? prev.filter(id => id !== catId) : [...prev, catId]);
|
||||
};
|
||||
|
||||
const collapseAll = () => setExpandedCategories([]);
|
||||
const expandAll = () => setExpandedCategories(sidebarCategories.map(c => c.id));
|
||||
|
||||
// Render content based on selection
|
||||
// Today's focus
|
||||
const todayTasks = projectTasks.filter(t => t.priority === "critical" || t.status === "in_progress").slice(0, 3);
|
||||
|
||||
// Export function
|
||||
const exportTasks = () => {
|
||||
const data = JSON.stringify(projectTasks, null, 2);
|
||||
const blob = new Blob([data], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `tasks-${new Date().toISOString().split("T")[0]}.json`;
|
||||
a.click();
|
||||
};
|
||||
|
||||
// Add task
|
||||
const handleAddTask = () => {
|
||||
if (!newTaskTitle.trim()) return;
|
||||
addTask({ title: newTaskTitle, description: "", status: "todo", priority: "medium", project: newTaskProject as any });
|
||||
setShowAddTask(false);
|
||||
setNewTaskTitle("");
|
||||
};
|
||||
|
||||
// Render functions
|
||||
const renderSidebar = () => (
|
||||
<aside className="w-64 border-r border-white/10 bg-[#1a1625] p-4">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<button onClick={collapseAll} className="w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-white/60 hover:bg-white/5 hover:text-white transition">⊖ Collapse All</button>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
|
||||
const renderHeader = () => (
|
||||
<header className="flex items-center justify-between mb-6 pb-4 border-b border-white/10">
|
||||
<div className="flex items-center gap-4">
|
||||
<button onClick={() => { setSelectedItem("sitemente"); setExpandedCategories(["projects"]); }} className="w-10 h-10 rounded-xl bg-brand-pink flex items-center justify-center text-xl hover:bg-[#ff7bc0] transition">👁️</button>
|
||||
<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>
|
||||
</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>
|
||||
);
|
||||
|
||||
const renderContent = () => {
|
||||
const category = currentItem?.category;
|
||||
|
||||
if (category === "chat") {
|
||||
return (
|
||||
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">
|
||||
<VoiceChat />
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
<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 text-sm text-white/70"><p>Memory is stored in:</p><ul className="text-white/50 space-y-1"><li>• localStorage (browser)</li><li>• GitHub repo (daily commits)</li><li>• MEMORY.md (curated)</li></ul></div>
|
||||
</div>
|
||||
);
|
||||
return renderTasksView();
|
||||
};
|
||||
|
||||
const renderTasksView = () => (
|
||||
<>
|
||||
{/* Stats Row */}
|
||||
<div className="mb-6 grid grid-cols-4 gap-3">
|
||||
{todayTasks.length > 0 && (
|
||||
<div className="mb-6 p-4 rounded-xl border border-brand-pink/30 bg-brand-pink/10">
|
||||
<p className="text-xs text-brand-pink font-medium mb-2">🎯 TODAY'S FOCUS</p>
|
||||
<div className="space-y-2">{todayTasks.map(task => (
|
||||
<div key={task.id} className="flex items-center gap-2 text-sm">
|
||||
<span className={`w-2 h-2 rounded-full ${task.status === "in_progress" ? "bg-yellow-400" : "bg-red-400"}`} />
|
||||
<span className="truncate">{task.title}</span>
|
||||
</div>
|
||||
))}</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="flex-1 relative">
|
||||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-white/40">🔍</span>
|
||||
<input id="search-input" type="text" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder="Search tasks... (press /)" className="w-full bg-white/10 border border-white/20 rounded-lg pl-9 pr-4 py-2 text-sm placeholder:text-white/40 focus:outline-none focus:border-brand-pink" />
|
||||
</div>
|
||||
<button onClick={() => setShowAddTask(!showAddTask)} className="px-4 py-2 bg-brand-pink rounded-lg text-sm font-medium hover:bg-[#ff7bc0] transition">+ Add</button>
|
||||
<button onClick={exportTasks} className="px-3 py-2 bg-white/10 rounded-lg text-sm hover:bg-white/20 transition">📥</button>
|
||||
</div>
|
||||
{showAddTask && (
|
||||
<div className="mb-4 p-4 rounded-xl border border-brand-pink/30 bg-brand-pink/5">
|
||||
<input type="text" value={newTaskTitle} onChange={(e) => setNewTaskTitle(e.target.value)} placeholder="New task title..." className="w-full bg-white/10 border border-white/20 rounded-lg px-4 py-2 text-sm placeholder:text-white/40 focus:outline-none focus:border-brand-pink mb-3" autoFocus onKeyDown={(e) => e.key === "Enter" && handleAddTask()} />
|
||||
<div className="flex items-center gap-2">
|
||||
<select value={newTaskProject} onChange={(e) => setNewTaskProject(e.target.value)} className="bg-white/10 border border-white/20 rounded-lg px-3 py-1.5 text-sm">
|
||||
<option value="sitemente">SiteMente</option><option value="holacompi">HolaCompi</option><option value="infrastructure">Infrastructure</option>
|
||||
</select>
|
||||
<button onClick={handleAddTask} className="px-4 py-1.5 bg-brand-pink rounded-lg text-sm font-medium">Add Task</button>
|
||||
<button onClick={() => setShowAddTask(false)} className="px-3 py-1.5 text-sm text-white/60 hover:text-white">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-4 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"
|
||||
}`}
|
||||
>
|
||||
<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>
|
||||
<h2 className="font-semibold">{currentItem?.name || "Tasks"}{searchQuery && <span className="ml-2 text-white/50 text-sm">({searchedTasks.length} results)</span>}</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) => {
|
||||
{searchedTasks.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>
|
||||
)}
|
||||
<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>
|
||||
<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>
|
||||
)}
|
||||
{searchedTasks.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>
|
||||
<button onClick={collapseAll} className="w-full flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-white/60 hover:bg-white/5 hover:text-white transition">
|
||||
⊖ Collapse All
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Main Content */}
|
||||
{renderSidebar()}
|
||||
<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 className="flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => { setSelectedItem("sitemente"); setExpandedCategories(["projects"]); }}
|
||||
className="w-10 h-10 rounded-xl bg-brand-pink flex items-center justify-center text-xl hover:bg-[#ff7bc0] transition"
|
||||
>
|
||||
👁️
|
||||
</button>
|
||||
<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>
|
||||
</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 */}
|
||||
{renderHeader()}
|
||||
{renderContent()}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user