Seed: product-management template (23 files)

This commit is contained in:
2026-07-23 22:14:57 +00:00
parent 3d0faf1e7b
commit f78bc6f622
+175
View File
@@ -0,0 +1,175 @@
import { useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import {
Plus, ThumbsUp, GripVertical, MoreHorizontal, Sparkles
} from "lucide-react";
import { toast } from "sonner";
import Sidebar from "@/components/Sidebar";
import Header from "@/components/Header";
interface Feature {
id: number;
title: string;
description: string;
priority: string;
priorityColor: string;
votes: number;
status: string;
statusColor: string;
assignee: string;
assigneeColor: string;
}
interface RoadmapColumn {
id: string;
title: string;
color: string;
dotColor: string;
features: Feature[];
}
const initialColumns: RoadmapColumn[] = [
{
id: "now",
title: "Now",
color: "bg-blue-50 border-blue-200",
dotColor: "bg-blue-600",
features: [
{ id: 1, title: "Visual Roadmap Board", description: "Drag-and-drop interface for managing product priorities and timelines", priority: "P0", priorityColor: "bg-red-100 text-red-700", votes: 48, status: "In Progress", statusColor: "bg-blue-100 text-blue-700", assignee: "SK", assigneeColor: "bg-blue-500" },
{ id: 2, title: "Slack Integration", description: "Integrate with Slack, Jira, and Linear for seamless workflow", priority: "P0", priorityColor: "bg-red-100 text-red-700", votes: 36, status: "In Review", statusColor: "bg-amber-100 text-amber-700", assignee: "AL", assigneeColor: "bg-sky-500" },
{ id: 3, title: "Priority Scoring", description: "Customizable scoring model with weighted criteria", priority: "P1", priorityColor: "bg-orange-100 text-orange-700", votes: 29, status: "In Progress", statusColor: "bg-blue-100 text-blue-700", assignee: "RJ", assigneeColor: "bg-indigo-500" },
],
},
{
id: "next",
title: "Next",
color: "bg-sky-50 border-sky-200",
dotColor: "bg-sky-500",
features: [
{ id: 4, title: "Feedback Analytics", description: "AI-powered analysis of user feedback with sentiment detection", priority: "P1", priorityColor: "bg-orange-100 text-orange-700", votes: 52, status: "Planned", statusColor: "bg-slate-100 text-slate-700", assignee: "NK", assigneeColor: "bg-emerald-500" },
{ id: 5, title: "Public Changelog", description: "Public-facing changelog with email subscription support", priority: "P1", priorityColor: "bg-orange-100 text-orange-700", votes: 31, status: "Planned", statusColor: "bg-slate-100 text-slate-700", assignee: "SK", assigneeColor: "bg-blue-500" },
{ id: 6, title: "API Access", description: "REST and GraphQL APIs for programmatic access to product data", priority: "P2", priorityColor: "bg-slate-100 text-slate-600", votes: 22, status: "Planned", statusColor: "bg-slate-100 text-slate-700", assignee: "AL", assigneeColor: "bg-sky-500" },
{ id: 7, title: "Custom Dashboards", description: "Build personalized views with drag-and-drop widgets", priority: "P2", priorityColor: "bg-slate-100 text-slate-600", votes: 18, status: "Planned", statusColor: "bg-slate-100 text-slate-700", assignee: "RJ", assigneeColor: "bg-indigo-500" },
],
},
{
id: "later",
title: "Later",
color: "bg-slate-50 border-slate-200",
dotColor: "bg-slate-400",
features: [
{ id: 8, title: "Mobile App", description: "Native iOS and Android apps for on-the-go product management", priority: "P2", priorityColor: "bg-slate-100 text-slate-600", votes: 41, status: "Exploring", statusColor: "bg-purple-100 text-purple-700", assignee: "NK", assigneeColor: "bg-emerald-500" },
{ id: 9, title: "AI Roadmap Generator", description: "Auto-generate roadmaps from feedback patterns and market data", priority: "P2", priorityColor: "bg-slate-100 text-slate-600", votes: 33, status: "Exploring", statusColor: "bg-purple-100 text-purple-700", assignee: "SK", assigneeColor: "bg-blue-500" },
{ id: 10, title: "White-label Portal", description: "Customer-facing portal with custom branding and domain", priority: "P2", priorityColor: "bg-slate-100 text-slate-600", votes: 19, status: "Exploring", statusColor: "bg-purple-100 text-purple-700", assignee: "AL", assigneeColor: "bg-sky-500" },
],
},
];
export default function IndexPage() {
const [columns, setColumns] = useState(initialColumns);
const handleVote = (columnId: string, featureId: number) => {
setColumns(prev => prev.map(col =>
col.id === columnId
? { ...col, features: col.features.map(f => f.id === featureId ? { ...f, votes: f.votes + 1 } : f) }
: col
));
toast("Vote recorded!");
};
const handleAddFeature = (columnId: string) => {
toast("Add feature dialog would open here");
};
return (
<div className="flex min-h-screen bg-slate-50">
<Sidebar activePath="/" />
<div className="flex-1 lg:ml-64">
<Header />
<main className="p-6 space-y-6">
{/* ── Page Header ── */}
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-bold text-slate-900 animate-fade-up">Roadmap</h1>
<p className="text-slate-500 mt-1">Plan and communicate your product strategy</p>
</div>
<div className="flex items-center gap-3">
<Button variant="outline" className="border-slate-200">
<Sparkles className="mr-2 h-4 w-4 text-blue-600" /> AI Suggest
</Button>
<Button className="bg-blue-600 hover:bg-blue-700 text-white">
<Plus className="mr-2 h-4 w-4" /> Add Feature
</Button>
</div>
</div>
{/* ── Roadmap Columns ── */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 stagger">
{columns.map((column) => (
<div key={column.id} className="space-y-4">
{/* Column Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className={`h-2.5 w-2.5 rounded-full \${column.dotColor}`} />
<h2 className="font-semibold text-slate-900">{column.title}</h2>
<Badge variant="secondary" className="text-xs">{column.features.length}</Badge>
</div>
<Button variant="ghost" size="sm" className="h-8 w-8 p-0 text-slate-400" onClick={() => handleAddFeature(column.id)}>
<Plus className="h-4 w-4" />
</Button>
</div>
{/* Feature Cards */}
<div className="space-y-3">
{column.features.map((feature) => (
<Card key={feature.id} className={`border shadow-sm backdrop-blur-sm bg-card/80 border-border/50 hover:-translate-y-1 hover:shadow-lg transition-all duration-300 cursor-pointer group \${column.color}`}>
<CardContent className="p-4">
<div className="flex items-start justify-between mb-2">
<h3 className="font-medium text-slate-900 text-sm leading-tight">{feature.title}</h3>
<Button variant="ghost" size="sm" className="h-6 w-6 p-0 text-slate-400 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
<MoreHorizontal className="h-3.5 w-3.5" />
</Button>
</div>
<p className="text-xs text-slate-500 mb-3 line-clamp-2">{feature.description}</p>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Badge className={`\${feature.priorityColor} text-[10px] font-semibold border-0 px-1.5 py-0`}>{feature.priority}</Badge>
<Badge className={`\${feature.statusColor} text-[10px] font-medium border-0 px-1.5 py-0`}>{feature.status}</Badge>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => handleVote(column.id, feature.id)}
className="flex items-center gap-1 text-xs text-slate-400 hover:text-blue-600 transition-colors"
>
<ThumbsUp className="h-3 w-3" />
<span>{feature.votes}</span>
</button>
<Avatar className="h-5 w-5">
<AvatarFallback className={`text-[8px] text-white \${feature.assigneeColor}`}>{feature.assignee}</AvatarFallback>
</Avatar>
</div>
</div>
</CardContent>
</Card>
))}
</div>
{/* Add Feature Button */}
<Button
variant="ghost"
className="w-full border-2 border-dashed border-slate-200 text-slate-400 hover:text-blue-600 hover:border-blue-300 hover:bg-blue-50/50"
onClick={() => handleAddFeature(column.id)}
>
<Plus className="mr-2 h-4 w-4" /> Add Feature
</Button>
</div>
))}
</div>
</main>
</div>
</div>
);
}