From cc84c0953d31132040a7c55d6094c119da6bca6b Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Sat, 15 Nov 2025 09:28:55 +0000 Subject: [PATCH] Create SprintWidget for GAMEFORGE dashboard cgen-8e5e7fb51ebb463388ae34d7348edc28 --- client/components/SprintWidget.tsx | 173 +++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 client/components/SprintWidget.tsx diff --git a/client/components/SprintWidget.tsx b/client/components/SprintWidget.tsx new file mode 100644 index 00000000..d2b2d5cc --- /dev/null +++ b/client/components/SprintWidget.tsx @@ -0,0 +1,173 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Clock, Zap, TrendingUp } from "lucide-react"; + +interface SprintWidget { + id: string; + project_name?: string; + sprint_number?: number; + start_date: string; + end_date: string; + status: "planning" | "active" | "completed"; + scope?: string; + team_size?: number; + tasks_total?: number; + tasks_completed?: number; +} + +interface SprintWidgetProps { + sprint: SprintWidget | null; + accentColor?: "green" | "blue" | "purple"; +} + +const colorMap = { + green: { + bg: "bg-gradient-to-br from-green-950/40 to-green-900/20", + border: "border-green-500/20", + accent: "text-green-400", + }, + blue: { + bg: "bg-gradient-to-br from-blue-950/40 to-blue-900/20", + border: "border-blue-500/20", + accent: "text-blue-400", + }, + purple: { + bg: "bg-gradient-to-br from-purple-950/40 to-purple-900/20", + border: "border-purple-500/20", + accent: "text-purple-400", + }, +}; + +export function SprintWidgetComponent({ + sprint, + accentColor = "green", +}: SprintWidgetProps) { + const colors = colorMap[accentColor]; + + if (!sprint) { + return ( + + + + + Active Sprint + + Sprint timeline and countdown + + +
+ +

No active sprint

+
+
+
+ ); + } + + const now = new Date(); + const endDate = new Date(sprint.end_date); + const daysRemaining = Math.ceil((endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); + const isActive = sprint.status === "active"; + const progress = sprint.tasks_total && sprint.tasks_total > 0 + ? Math.round(((sprint.tasks_completed || 0) / sprint.tasks_total) * 100) + : 0; + + const formatCountdown = (days: number) => { + if (days < 0) return "Sprint Over"; + if (days === 0) return "Today"; + if (days === 1) return "Tomorrow"; + return `${days} days left`; + }; + + return ( + + +
+
+ + + {sprint.project_name || "Sprint"} + {sprint.sprint_number && #{sprint.sprint_number}} + + Sprint timeline and progress +
+ + {sprint.status} + +
+
+ + {/* Countdown */} +
+
+

Days Remaining

+ +
+
+ {daysRemaining > 0 ? daysRemaining : 0} days +
+

{formatCountdown(daysRemaining)}

+
+ + {/* Timeline */} +
+

Timeline

+
+
+

Start

+

+ {new Date(sprint.start_date).toLocaleDateString()} +

+
+
+

End

+

+ {new Date(sprint.end_date).toLocaleDateString()} +

+
+
+
+ + {/* Task Progress */} + {sprint.tasks_total && sprint.tasks_total > 0 && ( +
+
+

Task Progress

+

{progress}%

+
+
+
+
+

+ {sprint.tasks_completed || 0} of {sprint.tasks_total} tasks completed +

+
+ )} + + {/* Scope */} + {sprint.scope && ( +
+

Scope

+

{sprint.scope}

+
+ )} + + {/* Team */} + {sprint.team_size && ( +
+
+

Team Size

+

{sprint.team_size} members

+
+ +
+ )} + + + ); +} + +export default SprintWidgetComponent;