feat: New MC sidebar - Projects, Tasks, Chat, Council, Calendar, Memory
This commit is contained in:
@@ -6,47 +6,70 @@ import { TaskStatus } from "@/lib/mission-control/types";
|
|||||||
import VoiceChat from "./VoiceChat";
|
import VoiceChat from "./VoiceChat";
|
||||||
import AIManagement from "@/components/ai-management/AIManagement";
|
import AIManagement from "@/components/ai-management/AIManagement";
|
||||||
|
|
||||||
interface ProjectSummary {
|
interface SidebarItem {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
|
||||||
status: "active" | "paused" | "completed";
|
|
||||||
color: string;
|
|
||||||
icon: string;
|
icon: string;
|
||||||
|
color?: string;
|
||||||
|
category: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const projects: ProjectSummary[] = [
|
interface SidebarCategory {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
items: SidebarItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sidebarCategories: SidebarCategory[] = [
|
||||||
{
|
{
|
||||||
id: "sitemente",
|
id: "projects",
|
||||||
name: "SiteMente",
|
name: "Projects",
|
||||||
description: "AI website platform for local businesses (B2B)",
|
icon: "🎯",
|
||||||
status: "active",
|
items: [
|
||||||
color: "#ff7bc0",
|
{ id: "sitemente", name: "SiteMente", icon: "🌐", color: "#ff7bc0", category: "projects" },
|
||||||
icon: "🌐",
|
{ id: "holacompi", name: "HolaCompi", icon: "🤝", color: "#6366f1", category: "projects" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "holacompi",
|
id: "tasks",
|
||||||
name: "HolaCompi",
|
name: "Tasks",
|
||||||
description: "AI ally for immigrants/consumers (B2C)",
|
icon: "✓",
|
||||||
status: "paused",
|
items: [
|
||||||
color: "#6366f1",
|
{ id: "all", name: "All Tasks", icon: "📋", category: "tasks" },
|
||||||
icon: "🤝",
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "infrastructure",
|
id: "chat",
|
||||||
name: "Infrastructure",
|
name: "Chat",
|
||||||
description: "Security, backups, APIs, and system config",
|
icon: "💬",
|
||||||
status: "active",
|
items: [
|
||||||
color: "#10b981",
|
{ id: "voice", name: "Voice Chat", icon: "🎤", category: "chat" },
|
||||||
icon: "🔒",
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "horus",
|
id: "council",
|
||||||
name: "Horus AI",
|
name: "Council",
|
||||||
description: "Manage my skills, APIs, and automation",
|
icon: "🏛️",
|
||||||
status: "active",
|
items: [
|
||||||
color: "#f59e0b",
|
{ id: "ai-settings", name: "AI Settings", icon: "🤖", category: "council" },
|
||||||
icon: "🤖",
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -58,175 +81,85 @@ const statusConfig: Record<TaskStatus, { label: string; color: string }> = {
|
|||||||
paused: { label: "Paused", color: "text-gray-400" },
|
paused: { label: "Paused", color: "text-gray-400" },
|
||||||
};
|
};
|
||||||
|
|
||||||
type ViewType = "tasks" | "horus";
|
|
||||||
|
|
||||||
export default function MissionControlDashboard() {
|
export default function MissionControlDashboard() {
|
||||||
const { tasks, toggleTask, updateTaskStatus, getProjectProgress, getTasksByProject } =
|
const { tasks, toggleTask, updateTaskStatus, getProjectProgress, getTasksByProject } = useMissionControl();
|
||||||
useMissionControl();
|
|
||||||
|
|
||||||
const [selectedProject, setSelectedProject] = useState<string>("sitemente");
|
const [selectedItem, setSelectedItem] = useState<string>("sitemente");
|
||||||
const [filter, setFilter] = useState<TaskStatus | "all">("all");
|
const [filter, setFilter] = useState<TaskStatus | "all">("all");
|
||||||
const [view, setView] = useState<ViewType>("tasks");
|
const [expandedCategories, setExpandedCategories] = useState<string[]>(["projects"]);
|
||||||
|
|
||||||
const projectTasks = getTasksByProject(selectedProject as any);
|
// 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 filteredTasks = filter === "all" ? projectTasks : projectTasks.filter((t) => t.status === filter);
|
||||||
const progress = getProjectProgress(selectedProject as any);
|
const progress = currentItem?.category === "projects" ? getProjectProgress(selectedItem as any) : 0;
|
||||||
|
|
||||||
const selectedProjectData = projects.find((p) => p.id === selectedProject)!;
|
const toggleCategory = (catId: string) => {
|
||||||
|
setExpandedCategories(prev =>
|
||||||
|
prev.includes(catId) ? prev.filter(id => id !== catId) : [...prev, catId]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// If viewing Horus AI management
|
// Render content based on selection
|
||||||
if (view === "horus") {
|
const renderContent = () => {
|
||||||
|
const category = currentItem?.category;
|
||||||
|
|
||||||
|
if (category === "chat") {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[#1a1625] text-white">
|
<div className="rounded-xl border border-white/10 bg-white/5 p-6">
|
||||||
{/* 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 />
|
<VoiceChat />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (category === "council") {
|
||||||
|
return (
|
||||||
|
<div className="rounded-xl border border-white/10 bg-white/5 p-6">
|
||||||
|
<AIManagement />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (category === "calendar") {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[#1a1625] text-white">
|
<div className="space-y-4">
|
||||||
{/* Header */}
|
<div className="rounded-xl border border-white/10 bg-white/5 p-6">
|
||||||
<header className="border-b border-white/10 bg-[#2d2640] px-6 py-4">
|
<h3 className="text-lg font-semibold mb-4">📅 Morning Brief</h3>
|
||||||
<div className="mx-auto flex max-w-7xl items-center justify-between">
|
<p className="text-white/60 mb-4">Daily intelligence at 6am CET</p>
|
||||||
<div className="flex items-center gap-4">
|
<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">
|
||||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-brand-pink text-xl">
|
☀️ Open Calendar
|
||||||
👁️
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<VoiceChat />
|
||||||
<h1 className="text-xl font-bold">Mission Control</h1>
|
|
||||||
<p className="text-xs text-white/60">SiteMente + HolaCompi + Infrastructure</p>
|
|
||||||
</div>
|
</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 */}
|
if (category === "memory") {
|
||||||
<div className="mt-8 pt-6 border-t border-white/10">
|
return (
|
||||||
<p className="text-xs text-white/50 uppercase mb-3">Quick Links</p>
|
<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">
|
<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">
|
<p className="text-sm text-white/70">Memory is stored in:</p>
|
||||||
☀️ Morning Brief
|
<ul className="text-sm text-white/50 space-y-1">
|
||||||
</a>
|
<li>• localStorage (browser)</li>
|
||||||
<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">
|
<li>• GitHub repo (daily commits)</li>
|
||||||
🏠 SiteMente Site
|
<li>• MEMORY.md (curated)</li>
|
||||||
</a>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
|
||||||
|
|
||||||
|
// Default: Tasks view
|
||||||
|
return renderTasksView();
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTasksView = () => (
|
||||||
|
<>
|
||||||
{/* Stats Row */}
|
{/* Stats Row */}
|
||||||
<div className="mb-6 grid grid-cols-4 gap-3">
|
<div className="mb-6 grid grid-cols-4 gap-3">
|
||||||
{(["todo", "in_progress", "done", "blocked"] as TaskStatus[]).map((status) => {
|
{(["todo", "in_progress", "done", "blocked"] as TaskStatus[]).map((status) => {
|
||||||
@@ -237,9 +170,7 @@ export default function MissionControlDashboard() {
|
|||||||
key={status}
|
key={status}
|
||||||
onClick={() => setFilter(filter === status ? "all" : status)}
|
onClick={() => setFilter(filter === status ? "all" : status)}
|
||||||
className={`rounded-xl border p-3 text-center transition ${
|
className={`rounded-xl border p-3 text-center transition ${
|
||||||
filter === status
|
filter === status ? "border-white/40 bg-white/10" : "border-white/10 bg-white/5 hover:border-white/20"
|
||||||
? "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-xl font-bold ${config.color}`}>{count}</p>
|
||||||
@@ -253,29 +184,25 @@ export default function MissionControlDashboard() {
|
|||||||
<div className="rounded-xl border border-white/10 bg-white/5">
|
<div className="rounded-xl border border-white/10 bg-white/5">
|
||||||
<div className="border-b border-white/10 px-4 py-3">
|
<div className="border-b border-white/10 px-4 py-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="font-semibold">{selectedProjectData?.name} Tasks</h2>
|
<h2 className="font-semibold">{currentItem?.name || "Tasks"}</h2>
|
||||||
<p className="text-sm text-white/50">
|
<p className="text-sm text-white/50">
|
||||||
{filteredTasks.filter((t) => t.status === "done").length} / {filteredTasks.length} done
|
{filteredTasks.filter((t) => t.status === "done").length} / {filteredTasks.length} done
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="divide-y divide-white/5 max-h-[400px] overflow-y-auto">
|
<div className="divide-y divide-white/5 max-h-[500px] overflow-y-auto">
|
||||||
{filteredTasks.map((task) => {
|
{filteredTasks.map((task) => {
|
||||||
const config = statusConfig[task.status];
|
const config = statusConfig[task.status];
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={task.id}
|
key={task.id}
|
||||||
className={`flex items-center gap-3 px-4 py-3 transition hover:bg-white/5 ${
|
className={`flex items-center gap-3 px-4 py-3 transition hover:bg-white/5 ${task.status === "done" ? "opacity-50" : ""}`}
|
||||||
task.status === "done" ? "opacity-50" : ""
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
onClick={() => toggleTask(task.id)}
|
onClick={() => toggleTask(task.id)}
|
||||||
className={`flex-shrink-0 w-5 h-5 rounded-full border-2 flex items-center justify-center transition ${
|
className={`flex-shrink-0 w-5 h-5 rounded-full border-2 flex items-center justify-center transition ${
|
||||||
task.status === "done"
|
task.status === "done" ? "border-green-500 bg-green-500 text-white" : "border-white/30 hover:border-white/50"
|
||||||
? "border-green-500 bg-green-500 text-white"
|
|
||||||
: "border-white/30 hover:border-white/50"
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{task.status === "done" && (
|
{task.status === "done" && (
|
||||||
@@ -292,9 +219,7 @@ export default function MissionControlDashboard() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{task.priority === "critical" && (
|
{task.priority === "critical" && (
|
||||||
<span className="px-2 py-0.5 rounded-full bg-red-500/20 text-red-400 text-xs">
|
<span className="px-2 py-0.5 rounded-full bg-red-500/20 text-red-400 text-xs">CRITICAL</span>
|
||||||
CRITICAL
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<select
|
<select
|
||||||
@@ -318,14 +243,94 @@ export default function MissionControlDashboard() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
{/* Voice Chat */}
|
return (
|
||||||
<div className="mt-6">
|
<div className="min-h-screen bg-[#1a1625] text-white flex">
|
||||||
<VoiceChat />
|
{/* 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>
|
||||||
|
</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>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user