diff --git a/spark.meta.json b/spark.meta.json
index 3769e33..fd74d91 100644
--- a/spark.meta.json
+++ b/spark.meta.json
@@ -1,6 +1,4 @@
-{
- "templateVersion": 0,
- "dbType": null
-} "templateVersion": 0,
- "dbType": null
+{
+ "templateVersion": 0,
+ "dbType": null
}
\ No newline at end of file
diff --git a/src/components/ProductRoadmap.tsx b/src/components/ProductRoadmap.tsx
index 22192c8..0cae850 100644
--- a/src/components/ProductRoadmap.tsx
+++ b/src/components/ProductRoadmap.tsx
@@ -4,8 +4,15 @@ import { Checkbox } from '@/components/ui/checkbox'
import { Badge } from '@/components/ui/badge'
import { Progress } from '@/components/ui/progress'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
-import { CheckCircle, Circle, Target, ChartBar, Users, Database, Rocket, Shield } from '@phosphor-icons/react'
-import { useState, useEffect } from 'react'
+import { Button } from '@/components/ui/button'
+import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+import { Textarea } from '@/components/ui/textarea'
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
+import { CheckCircle, Circle, Target, ChartBar, Users, Database, Rocket, Shield, Plus, FunnelSimple, CalendarBlank, ChatCircleText } from '@phosphor-icons/react'
+import { useState } from 'react'
+import { toast } from 'sonner'
interface RoadmapFeature {
id: string
@@ -14,6 +21,9 @@ interface RoadmapFeature {
category: 'strategy-cards' | 'workbench' | 'cross-product' | 'portfolio' | 'integration' | 'opex' | 'reporting' | 'non-functional'
priority: 'critical' | 'high' | 'medium' | 'low'
completed: boolean
+ estimatedDate?: string
+ completedDate?: string
+ notes?: string
}
const initialFeatures: RoadmapFeature[] = [
@@ -360,31 +370,93 @@ const priorityColors = {
export default function ProductRoadmap() {
const [features, setFeatures] = useKV('product-roadmap-features', initialFeatures)
const [selectedCategory, setSelectedCategory] = useState('all')
+ const [filterPriority, setFilterPriority] = useState('all')
+ const [filterStatus, setFilterStatus] = useState('all')
+ const [isAddDialogOpen, setIsAddDialogOpen] = useState(false)
+ const [editingFeature, setEditingFeature] = useState(null)
+ const [newFeature, setNewFeature] = useState({
+ name: '',
+ description: '',
+ category: 'strategy-cards' as const,
+ priority: 'medium' as const,
+ estimatedDate: '',
+ notes: ''
+ })
const toggleFeature = (featureId: string) => {
setFeatures((current) =>
(current || []).map(f =>
- f.id === featureId ? { ...f, completed: !f.completed } : f
+ f.id === featureId ? {
+ ...f,
+ completed: !f.completed,
+ completedDate: !f.completed ? new Date().toISOString().split('T')[0] : undefined
+ } : f
+ )
+ )
+ toast.success(features?.find(f => f.id === featureId)?.completed ? 'Feature reopened' : 'Feature completed!')
+ }
+
+ const addFeature = () => {
+ if (!newFeature.name.trim() || !newFeature.description.trim()) {
+ toast.error('Please fill in name and description')
+ return
+ }
+
+ const feature: RoadmapFeature = {
+ id: `custom-${Date.now()}`,
+ name: newFeature.name,
+ description: newFeature.description,
+ category: newFeature.category,
+ priority: newFeature.priority,
+ completed: false,
+ estimatedDate: newFeature.estimatedDate || undefined,
+ notes: newFeature.notes || undefined
+ }
+
+ setFeatures((current) => [...(current || []), feature])
+ setIsAddDialogOpen(false)
+ setNewFeature({
+ name: '',
+ description: '',
+ category: 'strategy-cards',
+ priority: 'medium',
+ estimatedDate: '',
+ notes: ''
+ })
+ toast.success('Feature added to roadmap!')
+ }
+
+ const updateFeatureNotes = (featureId: string, notes: string) => {
+ setFeatures((current) =>
+ (current || []).map(f =>
+ f.id === featureId ? { ...f, notes } : f
)
)
}
+ const filteredFeatures = (features || []).filter(f => {
+ if (filterPriority !== 'all' && f.priority !== filterPriority) return false
+ if (filterStatus === 'completed' && !f.completed) return false
+ if (filterStatus === 'in-progress' && f.completed) return false
+ return true
+ })
+
const categorizedFeatures = selectedCategory === 'all'
? Object.keys(categoryConfig).map(cat => ({
category: cat as keyof typeof categoryConfig,
- features: (features || []).filter(f => f.category === cat)
+ features: filteredFeatures.filter(f => f.category === cat)
}))
: [{
category: selectedCategory as keyof typeof categoryConfig,
- features: (features || []).filter(f => f.category === selectedCategory)
+ features: filteredFeatures.filter(f => f.category === selectedCategory)
}]
- const totalFeatures = (features || []).length
- const completedFeatures = (features || []).filter(f => f.completed).length
- const completionPercentage = Math.round((completedFeatures / totalFeatures) * 100)
+ const totalFeatures = filteredFeatures.length
+ const completedFeatures = filteredFeatures.filter(f => f.completed).length
+ const completionPercentage = totalFeatures > 0 ? Math.round((completedFeatures / totalFeatures) * 100) : 0
const categoryProgress = Object.keys(categoryConfig).map(cat => {
- const categoryFeatures = (features || []).filter(f => f.category === cat)
+ const categoryFeatures = filteredFeatures.filter(f => f.category === cat)
const completed = categoryFeatures.filter(f => f.completed).length
const total = categoryFeatures.length
return {
@@ -404,20 +476,162 @@ export default function ProductRoadmap() {
Track development progress of core features and capabilities
-
-
- Overall Progress
-
-
-
-
-
{completedFeatures} of {totalFeatures}
-
{completionPercentage}%
+
+
+
+ Overall Progress
+
+
+
+
+ {completedFeatures} of {totalFeatures}
+ {completionPercentage}%
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+ Filters:
+
+
+
+ {(filterPriority !== 'all' || filterStatus !== 'all') && (
+
+ )}
@@ -492,23 +706,89 @@ export default function ProductRoadmap() {
/>
-
-
- {feature.priority}
-
+
+
+
+
+ {feature.priority}
+
+ {feature.estimatedDate && (
+
+
+ Est: {new Date(feature.estimatedDate).toLocaleDateString()}
+
+ )}
+ {feature.completedDate && (
+
+
+ Done: {new Date(feature.completedDate).toLocaleDateString()}
+
+ )}
+
+
-
{feature.description}
+ {feature.notes && (
+
+ )}
+
{feature.completed ? (