From 49b0fc102916ee310b5f7433f2744803a7486a84 Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Wed, 12 Nov 2025 03:09:32 +0000 Subject: [PATCH] Create GameForge admin dashboard with projects, team, metrics, and KPIs cgen-d63f06ab2976476a830a5c8b28f8a2b9 --- .../components/admin/AdminGameForgeStudio.tsx | 498 ++++++++++++++++++ 1 file changed, 498 insertions(+) create mode 100644 client/components/admin/AdminGameForgeStudio.tsx diff --git a/client/components/admin/AdminGameForgeStudio.tsx b/client/components/admin/AdminGameForgeStudio.tsx new file mode 100644 index 00000000..74233366 --- /dev/null +++ b/client/components/admin/AdminGameForgeStudio.tsx @@ -0,0 +1,498 @@ +import { useEffect, useState } from "react"; +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/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/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 + + + )} + + +
+ ); +}