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

301 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 { motion } from "framer-motion";
import { useMissionControl } from "@/lib/mission-control/store";
import { TaskStatus } from "@/lib/mission-control/types";
import VoiceChat from "./VoiceChat";
interface ProjectSummary {
id: string;
name: string;
description: string;
status: "active" | "paused" | "completed";
color: string;
}
const projects: ProjectSummary[] = [
{
id: "sitemente",
name: "SiteMente",
description: "AI website platform for local businesses (B2B)",
status: "active",
color: "#ff7bc0",
},
{
id: "holacompi",
name: "HolaCompi",
description: "AI ally for immigrants/consumers (B2C)",
status: "paused",
color: "#6366f1",
},
{
id: "infrastructure",
name: "Infrastructure",
description: "Security, backups, APIs, and system config",
status: "active",
color: "#10b981",
},
];
const statusConfig: Record<TaskStatus, { label: string; color: string; bg: string }> = {
todo: { label: "To Do", color: "text-white/70", bg: "bg-white/10" },
in_progress: { label: "In Progress", color: "text-yellow-400", bg: "bg-yellow-500/20" },
done: { label: "Done", color: "text-green-400", bg: "bg-green-500/20" },
blocked: { label: "Blocked", color: "text-red-400", bg: "bg-red-500/20" },
paused: { label: "Paused", color: "text-gray-400", bg: "bg-gray-500/20" },
};
const fadeUp = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
};
export default function MissionControlDashboard() {
const { tasks, toggleTask, updateTaskStatus, getProjectProgress, getTasksByProject } =
useMissionControl();
const [selectedProject, setSelectedProject] = useState<"sitemente" | "holacompi" | "infrastructure">("sitemente");
const [filter, setFilter] = useState<TaskStatus | "all">("all");
const projectTasks = getTasksByProject(selectedProject);
const filteredTasks = filter === "all" ? projectTasks : projectTasks.filter((t) => t.status === filter);
const progress = getProjectProgress(selectedProject);
const selectedProjectData = projects.find((p) => p.id === selectedProject)!;
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</p>
</div>
</div>
<div className="flex items-center gap-4">
{/* Navigation */}
<nav className="flex items-center gap-2 mr-4">
<a
href="/mission-control"
className="px-3 py-1.5 rounded-lg bg-white/10 text-sm font-medium hover:bg-white/20 transition"
>
Tasks
</a>
<a
href="/morning-brief"
className="px-3 py-1.5 rounded-lg text-sm font-medium text-white/70 hover:bg-white/10 transition"
>
Brief
</a>
</nav>
<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>
<main className="mx-auto max-w-7xl px-6 py-8">
{/* Project Tabs */}
<div className="mb-8 flex gap-4">
{projects.map((project) => {
const p = getProjectProgress(project.id);
return (
<button
key={project.id}
onClick={() => setSelectedProject(project.id as "sitemente" | "holacompi")}
className={`relative flex-1 rounded-xl border p-4 text-left transition ${
selectedProject === project.id
? "border-white/30 bg-white/10"
: "border-white/10 bg-white/5 hover:border-white/20"
}`}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div
className="h-3 w-3 rounded-full"
style={{ backgroundColor: project.color }}
/>
<span className="font-semibold">{project.name}</span>
<span
className={`rounded-full px-2 py-0.5 text-xs ${
project.status === "active"
? "bg-green-500/20 text-green-400"
: "bg-yellow-500/20 text-yellow-400"
}`}
>
{project.status}
</span>
</div>
<span className="text-sm font-bold">{p}%</span>
</div>
<div className="mt-3 h-1.5 w-full rounded-full bg-white/10">
<div
className="h-full rounded-full transition-all duration-500"
style={{ width: `${p}%`, backgroundColor: project.color }}
/>
</div>
</button>
);
})}
</div>
{/* Stats Row */}
<div className="mb-8 grid grid-cols-4 gap-4">
{(["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-4 text-center transition ${
filter === status
? "border-white/40 bg-white/10"
: "border-white/10 bg-white/5 hover:border-white/20"
}`}
>
<p className={`text-2xl font-bold ${config.color}`}>{count}</p>
<p className="text-xs text-white/60">{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-6 py-4">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold">
{selectedProjectData.name} Tasks
</h2>
<p className="text-sm text-white/60">
{filteredTasks.filter((t) => t.status === "done").length} /{" "}
{filteredTasks.length} completed
</p>
</div>
</div>
<div className="divide-y divide-white/5">
{filteredTasks.map((task, index) => {
const config = statusConfig[task.status];
return (
<motion.div
key={task.id}
variants={fadeUp}
initial="hidden"
animate="visible"
transition={{ duration: 0.3, delay: index * 0.03 }}
className={`flex items-center gap-4 px-6 py-4 transition hover:bg-white/5 ${
task.status === "done" ? "opacity-50" : ""
}`}
>
<button
onClick={() => toggleTask(task.id)}
className={`flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full border-2 transition ${
task.status === "done"
? "border-green-500 bg-green-500 text-white"
: "border-white/30 hover:border-white/50"
}`}
>
{task.status === "done" && (
<svg className="h-3.5 w-3.5" 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">
<div className="flex items-center gap-2">
<p
className={`font-medium ${
task.status === "done" ? "line-through text-white/50" : ""
}`}
>
{task.title}
</p>
{task.priority === "critical" && (
<span className="rounded-full bg-red-500/20 px-2 py-0.5 text-xs text-red-400">
CRITICAL
</span>
)}
</div>
<p className="text-sm text-white/50 truncate">{task.description}</p>
</div>
<select
value={task.status}
onChange={(e) => updateTaskStatus(task.id, e.target.value as TaskStatus)}
className={`rounded-lg border border-white/20 bg-white/10 px-3 py-1.5 text-xs ${config.color} focus:border-white/40 focus:outline-none`}
>
<option value="todo">To Do</option>
<option value="in_progress">In Progress</option>
<option value="done">Done</option>
<option value="blocked">Blocked</option>
</select>
</motion.div>
);
})}
</div>
{filteredTasks.length === 0 && (
<div className="px-6 py-12 text-center text-white/50">
No tasks match the current filter.
</div>
)}
</div>
{/* Voice Chat */}
<div className="mt-8">
<VoiceChat />
</div>
{/* Quick Actions */}
<div className="mt-8 flex gap-4">
<button
onClick={() => {
const confirmed = window.confirm("Reset all tasks? This cannot be undone.");
if (confirmed && typeof window !== "undefined") {
localStorage.removeItem("sitemente:mission-control");
window.location.reload();
}
}}
className="rounded-lg border border-white/20 bg-white/5 px-4 py-2 text-sm text-white/70 hover:bg-white/10"
>
Reset All Tasks
</button>
</div>
</main>
</div>
);
}