import { useEffect, useState } from "react"; const API_BASE = import.meta.env.VITE_API_BASE || ""; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { BarChart, Bar, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, PieChart, Pie, Cell, } from "recharts"; import { Zap, Users, Gamepad2, TrendingUp, Package, Target, Clock, } from "lucide-react"; interface GameForgeProject { id: string; name: string; status: string; platform: string; team_size: number; budget: number; current_spend: number; target_release_date: string; actual_release_date: string; } interface TeamMember { id: string; user_id: string; role: string; position: string; contract_type: string; is_active: boolean; } interface GameForgeBuild { id: string; project_id: string; version: string; build_type: string; release_date: string; download_count: number; } interface MetricPoint { metric_date: string; velocity: number; team_size_avg: number; bugs_fixed: number; days_from_planned_to_release: number; } export default function AdminGameForgeStudio() { const [projects, setProjects] = useState([]); const [teamMembers, setTeamMembers] = useState([]); const [builds, setBuilds] = useState([]); const [metrics, setMetrics] = useState([]); const [selectedProject, setSelectedProject] = useState(""); const [loading, setLoading] = useState(true); useEffect(() => { fetchGameForgeData(); }, [selectedProject]); const fetchGameForgeData = async () => { try { setLoading(true); // Fetch projects const projectsRes = await fetch(`${API_BASE}/api/gameforge/projects`); if (projectsRes.ok) { const { data } = await projectsRes.json(); setProjects(data || []); if (data && data.length > 0 && !selectedProject) { setSelectedProject(data[0].id); } } // Fetch team members const teamRes = await fetch(`${API_BASE}/api/gameforge/team`); if (teamRes.ok) { const { data } = await teamRes.json(); setTeamMembers(data || []); } // Fetch builds and metrics if project selected if (selectedProject) { const buildsRes = await fetch( `/api/gameforge/builds?project_id=${selectedProject}`, ); if (buildsRes.ok) { const { data } = await buildsRes.json(); setBuilds(data || []); } const metricsRes = await fetch( `/api/gameforge/metrics?project_id=${selectedProject}`, ); if (metricsRes.ok) { const { data } = await metricsRes.json(); setMetrics(data || []); } } } catch (error) { console.error("Failed to fetch GameForge data:", error); } finally { setLoading(false); } }; const currentProject = projects.find((p) => p.id === selectedProject); // Calculate KPIs const totalTeamSize = teamMembers.filter((m) => m.is_active).length; const activeProjects = projects.filter( (p) => p.status === "in_development", ).length; const totalBudget = projects.reduce((sum, p) => sum + (p.budget || 0), 0); const totalSpent = projects.reduce( (sum, p) => sum + (p.current_spend || 0), 0, ); const budgetRemaining = totalBudget - totalSpent; // Calculate shipping velocity const avgShippingVelocity = metrics.length > 0 ? Math.round( metrics.reduce( (sum, m) => sum + (m.days_from_planned_to_release || 0), 0, ) / metrics.length, ) : 0; const onScheduleCount = projects.filter((p) => { if (!p.target_release_date || !p.actual_release_date) return false; const target = new Date(p.target_release_date); const actual = new Date(p.actual_release_date); return actual <= target; }).length; const statusColors: Record = { planning: "bg-blue-500/20 text-blue-400", in_development: "bg-yellow-500/20 text-yellow-400", qa: "bg-orange-500/20 text-orange-400", released: "bg-green-500/20 text-green-400", hiatus: "bg-gray-500/20 text-gray-400", cancelled: "bg-red-500/20 text-red-400", }; if (loading) { return
Loading...
; } return (
{/* KPI Cards */}
Active Projects
{activeProjects}

in development

Team Size
{totalTeamSize}

active members

Budget Health
{Math.round((budgetRemaining / totalBudget) * 100)}%

remaining

Ship Velocity
{avgShippingVelocity}d

from target

{/* Tabs */} Overview Projects Team Metrics {/* Overview Tab */} Budget Overview
Total Budget ${totalBudget.toLocaleString()}
Spent: ${totalSpent.toLocaleString()} Remaining: ${budgetRemaining.toLocaleString()}
Release Schedule {onScheduleCount} of {projects.length} projects on schedule
{projects.slice(0, 5).map((project) => { const isOnSchedule = project.actual_release_date && new Date(project.actual_release_date) <= new Date(project.target_release_date); return (

{project.name}

{project.status}

{isOnSchedule ? "On Time" : "Delayed"}
); })}
{/* Projects Tab */} Game Projects {projects.length} total projects
{projects.map((project) => (
setSelectedProject(project.id)} >

{project.name}

{project.status.replace("_", " ")}

Platform

{project.platform}

Team

{project.team_size} members

Budget

${project.budget?.toLocaleString() || "N/A"}

))}
{/* Team Tab */} Studio Team {teamMembers.filter((m) => m.is_active).length} active members
{teamMembers.map((member) => (

{member.position || member.role}

{!member.is_active && ( Inactive )}
{member.role} {member.contract_type}
))}
{/* Metrics Tab */} {selectedProject && metrics.length > 0 && ( <> Shipping Velocity Trend Days from planned to actual release Team Velocity Points/tasks completed per period )} {selectedProject && metrics.length === 0 && ( No metrics recorded yet for this project )}
); }