Seed: product-management template (23 files)
This commit is contained in:
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user