mirror of
https://github.com/johndoe6345789/strategy-execution-p.git
synced 2026-04-24 21:24:55 +00:00
Generated by Spark: Roadmap should have projects, i guess projects can have metrics and objectives
This commit is contained in:
@@ -1,8 +1,15 @@
|
||||
import { useKV } from '@github/spark/hooks'
|
||||
import { useState } from 'react'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import {
|
||||
Target,
|
||||
ChartBar,
|
||||
@@ -10,48 +17,18 @@ import {
|
||||
TrendUp,
|
||||
Gauge,
|
||||
ListChecks,
|
||||
ArrowsOut,
|
||||
CheckCircle,
|
||||
WarningCircle,
|
||||
XCircle,
|
||||
Circle
|
||||
Circle,
|
||||
Plus,
|
||||
FolderOpen,
|
||||
Trash,
|
||||
PencilSimple
|
||||
} from '@phosphor-icons/react'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
|
||||
interface Objective {
|
||||
id: string
|
||||
category: 'breakthrough' | 'annual' | 'improvement'
|
||||
description: string
|
||||
owner: string
|
||||
targetDate: string
|
||||
status: 'on-track' | 'at-risk' | 'completed' | 'not-started'
|
||||
metrics: Metric[]
|
||||
}
|
||||
|
||||
interface Metric {
|
||||
id: string
|
||||
name: string
|
||||
baseline: number
|
||||
current: number
|
||||
target: number
|
||||
unit: string
|
||||
frequency: 'monthly' | 'quarterly' | 'annual'
|
||||
lastUpdated: string
|
||||
trend: 'improving' | 'stable' | 'declining'
|
||||
}
|
||||
|
||||
interface BowlingChartData {
|
||||
objective: string
|
||||
months: MonthStatus[]
|
||||
}
|
||||
|
||||
interface MonthStatus {
|
||||
month: string
|
||||
status: 'green' | 'yellow' | 'red' | 'not-started'
|
||||
actual: number
|
||||
target: number
|
||||
}
|
||||
import { toast } from 'sonner'
|
||||
import type { RoadmapProject, RoadmapObjective, RoadmapMetric, BowlingChartData, StatusType, PriorityType } from '@/types'
|
||||
|
||||
interface XMatrixItem {
|
||||
id: string
|
||||
@@ -60,93 +37,264 @@ interface XMatrixItem {
|
||||
relationships: string[]
|
||||
}
|
||||
|
||||
const mockObjectives: Objective[] = [
|
||||
{
|
||||
id: 'obj-1',
|
||||
category: 'breakthrough',
|
||||
description: 'Achieve 25% revenue growth through new market expansion',
|
||||
owner: 'Chief Growth Officer',
|
||||
targetDate: '2025-12-31',
|
||||
status: 'on-track',
|
||||
metrics: [
|
||||
{
|
||||
id: 'm1',
|
||||
name: 'Revenue Growth',
|
||||
baseline: 100,
|
||||
current: 115,
|
||||
target: 125,
|
||||
unit: '%',
|
||||
frequency: 'quarterly',
|
||||
lastUpdated: '2025-01-15',
|
||||
trend: 'improving'
|
||||
},
|
||||
{
|
||||
id: 'm2',
|
||||
name: 'New Market Share',
|
||||
baseline: 0,
|
||||
current: 12,
|
||||
target: 20,
|
||||
unit: '%',
|
||||
frequency: 'monthly',
|
||||
lastUpdated: '2025-01-15',
|
||||
trend: 'improving'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'obj-2',
|
||||
category: 'annual',
|
||||
description: 'Reduce operational costs by 15% while maintaining quality',
|
||||
owner: 'Chief Operating Officer',
|
||||
targetDate: '2025-12-31',
|
||||
status: 'at-risk',
|
||||
metrics: [
|
||||
{
|
||||
id: 'm3',
|
||||
name: 'Cost Reduction',
|
||||
baseline: 100,
|
||||
current: 92,
|
||||
target: 85,
|
||||
unit: '%',
|
||||
frequency: 'monthly',
|
||||
lastUpdated: '2025-01-15',
|
||||
trend: 'stable'
|
||||
},
|
||||
{
|
||||
id: 'm4',
|
||||
name: 'Quality Score',
|
||||
baseline: 85,
|
||||
current: 87,
|
||||
target: 90,
|
||||
unit: 'pts',
|
||||
frequency: 'monthly',
|
||||
lastUpdated: '2025-01-15',
|
||||
trend: 'improving'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'obj-3',
|
||||
category: 'improvement',
|
||||
description: 'Improve customer satisfaction score to 95%',
|
||||
owner: 'Chief Customer Officer',
|
||||
targetDate: '2025-06-30',
|
||||
status: 'on-track',
|
||||
metrics: [
|
||||
{
|
||||
id: 'm5',
|
||||
name: 'NPS Score',
|
||||
baseline: 72,
|
||||
current: 84,
|
||||
target: 95,
|
||||
unit: 'pts',
|
||||
frequency: 'monthly',
|
||||
lastUpdated: '2025-01-15',
|
||||
trend: 'improving'
|
||||
}
|
||||
]
|
||||
function ProjectsView({ projects, setProjects }: { projects: RoadmapProject[], setProjects: (updater: (prev: RoadmapProject[]) => RoadmapProject[]) => void }) {
|
||||
const [isAddingProject, setIsAddingProject] = useState(false)
|
||||
const [editingProject, setEditingProject] = useState<RoadmapProject | null>(null)
|
||||
const [newProject, setNewProject] = useState<Partial<RoadmapProject>>({
|
||||
name: '',
|
||||
description: '',
|
||||
owner: '',
|
||||
status: 'not-started',
|
||||
priority: 'medium',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
progress: 0,
|
||||
objectives: [],
|
||||
metrics: []
|
||||
})
|
||||
|
||||
const handleAddProject = () => {
|
||||
if (!newProject.name || !newProject.owner || !newProject.startDate || !newProject.endDate) {
|
||||
toast.error('Please fill in all required fields')
|
||||
return
|
||||
}
|
||||
|
||||
const project: RoadmapProject = {
|
||||
id: `proj-${Date.now()}`,
|
||||
name: newProject.name,
|
||||
description: newProject.description || '',
|
||||
owner: newProject.owner,
|
||||
status: newProject.status as StatusType,
|
||||
priority: newProject.priority as PriorityType,
|
||||
startDate: newProject.startDate,
|
||||
endDate: newProject.endDate,
|
||||
progress: 0,
|
||||
objectives: [],
|
||||
metrics: []
|
||||
}
|
||||
|
||||
setProjects((prev) => [...prev, project])
|
||||
setIsAddingProject(false)
|
||||
setNewProject({
|
||||
name: '',
|
||||
description: '',
|
||||
owner: '',
|
||||
status: 'not-started',
|
||||
priority: 'medium',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
progress: 0,
|
||||
objectives: [],
|
||||
metrics: []
|
||||
})
|
||||
toast.success('Project created successfully')
|
||||
}
|
||||
]
|
||||
|
||||
const handleDeleteProject = (projectId: string) => {
|
||||
setProjects((prev) => prev.filter(p => p.id !== projectId))
|
||||
toast.success('Project deleted')
|
||||
}
|
||||
|
||||
const statusColors = {
|
||||
'not-started': 'bg-muted text-muted-foreground',
|
||||
'on-track': 'bg-success/10 text-success border-success/30',
|
||||
'at-risk': 'bg-warning/10 text-warning border-warning/30',
|
||||
'blocked': 'bg-destructive/10 text-destructive border-destructive/30',
|
||||
'completed': 'bg-primary/10 text-primary border-primary/30'
|
||||
}
|
||||
|
||||
const priorityColors = {
|
||||
'critical': 'bg-destructive/10 text-destructive border-destructive/30',
|
||||
'high': 'bg-warning/10 text-warning border-warning/30',
|
||||
'medium': 'bg-primary/10 text-primary border-primary/30',
|
||||
'low': 'bg-muted text-muted-foreground border-muted'
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold">Strategic Projects</h3>
|
||||
<p className="text-sm text-muted-foreground">Manage projects with objectives and metrics</p>
|
||||
</div>
|
||||
<Dialog open={isAddingProject} onOpenChange={setIsAddingProject}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="gap-2">
|
||||
<Plus size={20} weight="bold" />
|
||||
Add Project
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New Project</DialogTitle>
|
||||
<DialogDescription>Add a strategic project to track on the roadmap</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="name">Project Name *</Label>
|
||||
<Input
|
||||
id="name"
|
||||
value={newProject.name}
|
||||
onChange={(e) => setNewProject({ ...newProject, name: e.target.value })}
|
||||
placeholder="Enter project name"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<Textarea
|
||||
id="description"
|
||||
value={newProject.description}
|
||||
onChange={(e) => setNewProject({ ...newProject, description: e.target.value })}
|
||||
placeholder="Enter project description"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="owner">Owner *</Label>
|
||||
<Input
|
||||
id="owner"
|
||||
value={newProject.owner}
|
||||
onChange={(e) => setNewProject({ ...newProject, owner: e.target.value })}
|
||||
placeholder="Project owner"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="priority">Priority</Label>
|
||||
<Select
|
||||
value={newProject.priority}
|
||||
onValueChange={(value) => setNewProject({ ...newProject, priority: value as PriorityType })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="critical">Critical</SelectItem>
|
||||
<SelectItem value="high">High</SelectItem>
|
||||
<SelectItem value="medium">Medium</SelectItem>
|
||||
<SelectItem value="low">Low</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="startDate">Start Date *</Label>
|
||||
<Input
|
||||
id="startDate"
|
||||
type="date"
|
||||
value={newProject.startDate}
|
||||
onChange={(e) => setNewProject({ ...newProject, startDate: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="endDate">End Date *</Label>
|
||||
<Input
|
||||
id="endDate"
|
||||
type="date"
|
||||
value={newProject.endDate}
|
||||
onChange={(e) => setNewProject({ ...newProject, endDate: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="status">Status</Label>
|
||||
<Select
|
||||
value={newProject.status}
|
||||
onValueChange={(value) => setNewProject({ ...newProject, status: value as StatusType })}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="not-started">Not Started</SelectItem>
|
||||
<SelectItem value="on-track">On Track</SelectItem>
|
||||
<SelectItem value="at-risk">At Risk</SelectItem>
|
||||
<SelectItem value="blocked">Blocked</SelectItem>
|
||||
<SelectItem value="completed">Completed</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsAddingProject(false)}>Cancel</Button>
|
||||
<Button onClick={handleAddProject}>Create Project</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
{projects.length === 0 ? (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center justify-center py-12">
|
||||
<FolderOpen size={48} className="text-muted-foreground mb-4" />
|
||||
<p className="text-muted-foreground text-center">No projects yet. Create your first strategic project to get started.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid gap-4">
|
||||
{projects.map((project) => (
|
||||
<Card key={project.id}>
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<Badge variant="outline" className={`${statusColors[project.status]} capitalize font-semibold`}>
|
||||
{project.status.replace('-', ' ')}
|
||||
</Badge>
|
||||
<Badge variant="outline" className={`${priorityColors[project.priority]} capitalize font-semibold`}>
|
||||
{project.priority}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle className="text-lg">{project.name}</CardTitle>
|
||||
{project.description && (
|
||||
<CardDescription className="mt-2">{project.description}</CardDescription>
|
||||
)}
|
||||
<CardDescription className="mt-2">
|
||||
<div className="flex items-center gap-4 text-sm">
|
||||
<span><strong>Owner:</strong> {project.owner}</span>
|
||||
<span><strong>Timeline:</strong> {new Date(project.startDate).toLocaleDateString()} - {new Date(project.endDate).toLocaleDateString()}</span>
|
||||
</div>
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" variant="ghost" onClick={() => handleDeleteProject(project.id)}>
|
||||
<Trash size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-semibold">Overall Progress</span>
|
||||
<span className="text-sm font-mono">{project.progress}%</span>
|
||||
</div>
|
||||
<Progress value={project.progress} className="h-2" />
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-muted-foreground">Objectives:</span>
|
||||
<span className="ml-2 font-semibold">{project.objectives.length}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Metrics:</span>
|
||||
<span className="ml-2 font-semibold">{project.metrics.length}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const mockBowlingChart: BowlingChartData[] = [
|
||||
{
|
||||
@@ -202,7 +350,9 @@ const mockBowlingChart: BowlingChartData[] = [
|
||||
}
|
||||
]
|
||||
|
||||
function ObjectivesView() {
|
||||
function ObjectivesView({ projects }: { projects: RoadmapProject[] }) {
|
||||
const allObjectives = projects.flatMap(p => p.objectives)
|
||||
|
||||
const categoryColors = {
|
||||
breakthrough: 'bg-accent/10 text-accent border-accent/30',
|
||||
annual: 'bg-primary/10 text-primary border-primary/30',
|
||||
@@ -212,13 +362,25 @@ function ObjectivesView() {
|
||||
const statusIcons = {
|
||||
'on-track': <CheckCircle size={20} weight="fill" className="text-success" />,
|
||||
'at-risk': <WarningCircle size={20} weight="fill" className="text-warning" />,
|
||||
'blocked': <XCircle size={20} weight="fill" className="text-destructive" />,
|
||||
'completed': <CheckCircle size={20} weight="fill" className="text-success" />,
|
||||
'not-started': <Circle size={20} className="text-muted-foreground" />
|
||||
}
|
||||
|
||||
if (allObjectives.length === 0) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center justify-center py-12">
|
||||
<Target size={48} className="text-muted-foreground mb-4" />
|
||||
<p className="text-muted-foreground text-center">No objectives yet. Add projects with objectives to track them here.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{mockObjectives.map((objective) => (
|
||||
{allObjectives.map((objective) => (
|
||||
<Card key={objective.id}>
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
@@ -275,9 +437,9 @@ function ObjectivesView() {
|
||||
)
|
||||
}
|
||||
|
||||
function MetricsView() {
|
||||
const allMetrics = mockObjectives.flatMap(obj =>
|
||||
obj.metrics.map(m => ({ ...m, objective: obj.description }))
|
||||
function MetricsView({ projects }: { projects: RoadmapProject[] }) {
|
||||
const allMetrics = projects.flatMap(project =>
|
||||
project.metrics.map(m => ({ ...m, projectName: project.name }))
|
||||
)
|
||||
|
||||
const trendIcons = {
|
||||
@@ -286,6 +448,17 @@ function MetricsView() {
|
||||
declining: <TrendUp size={16} className="text-destructive rotate-180" weight="bold" />
|
||||
}
|
||||
|
||||
if (allMetrics.length === 0) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center justify-center py-12">
|
||||
<ChartBar size={48} className="text-muted-foreground mb-4" />
|
||||
<p className="text-muted-foreground text-center">No metrics yet. Add projects with metrics to track them here.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
@@ -308,7 +481,7 @@ function MetricsView() {
|
||||
<h4 className="font-semibold">{metric.name}</h4>
|
||||
{trendIcons[metric.trend]}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{metric.objective}</p>
|
||||
<p className="text-xs text-muted-foreground">{metric.projectName}</p>
|
||||
</div>
|
||||
<Badge variant="outline" className="font-mono shrink-0">
|
||||
{metric.frequency}
|
||||
@@ -520,16 +693,18 @@ function XMatrixView() {
|
||||
)
|
||||
}
|
||||
|
||||
function DashboardView() {
|
||||
function DashboardView({ projects }: { projects: RoadmapProject[] }) {
|
||||
const allObjectives = projects.flatMap(p => p.objectives)
|
||||
|
||||
const overallHealth = {
|
||||
onTrack: 2,
|
||||
atRisk: 1,
|
||||
offTrack: 0,
|
||||
notStarted: 0
|
||||
onTrack: allObjectives.filter(o => o.status === 'on-track').length,
|
||||
atRisk: allObjectives.filter(o => o.status === 'at-risk').length,
|
||||
offTrack: allObjectives.filter(o => o.status === 'blocked').length,
|
||||
notStarted: allObjectives.filter(o => o.status === 'not-started').length
|
||||
}
|
||||
|
||||
const total = Object.values(overallHealth).reduce((a, b) => a + b, 0)
|
||||
const healthPercent = Math.round((overallHealth.onTrack / total) * 100)
|
||||
const healthPercent = total > 0 ? Math.round((overallHealth.onTrack / total) * 100) : 0
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -590,7 +765,10 @@ function DashboardView() {
|
||||
<CardDescription>Progress across all strategic goals</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{mockObjectives.map((obj) => {
|
||||
{allObjectives.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground text-center py-4">No objectives to display</p>
|
||||
) : (
|
||||
allObjectives.map((obj) => {
|
||||
const avgProgress = obj.metrics.reduce((sum, m) => {
|
||||
const progress = ((m.current - m.baseline) / (m.target - m.baseline)) * 100
|
||||
return sum + progress
|
||||
@@ -607,7 +785,7 @@ function DashboardView() {
|
||||
<Progress value={avgProgress} className="h-2" />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
}))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -618,19 +796,21 @@ function DashboardView() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{['breakthrough', 'annual', 'improvement'].map((category) => {
|
||||
const objectives = mockObjectives.filter(obj => obj.category === category)
|
||||
const allMetrics = objectives.flatMap(obj => obj.metrics)
|
||||
const avgProgress = allMetrics.reduce((sum, m) => {
|
||||
const progress = ((m.current - m.baseline) / (m.target - m.baseline)) * 100
|
||||
return sum + progress
|
||||
}, 0) / allMetrics.length
|
||||
const objectives = allObjectives.filter(obj => obj.category === category)
|
||||
const categoryMetrics = objectives.flatMap(obj => obj.metrics)
|
||||
const avgProgress = categoryMetrics.length > 0
|
||||
? categoryMetrics.reduce((sum, m) => {
|
||||
const progress = ((m.current - m.baseline) / (m.target - m.baseline)) * 100
|
||||
return sum + progress
|
||||
}, 0) / categoryMetrics.length
|
||||
: 0
|
||||
|
||||
return (
|
||||
<div key={category} className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium capitalize">{category} Goals</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground">{allMetrics.length} metrics</span>
|
||||
<span className="text-xs text-muted-foreground">{categoryMetrics.length} metrics</span>
|
||||
<Badge variant="outline" className="font-mono">
|
||||
{Math.round(avgProgress)}%
|
||||
</Badge>
|
||||
@@ -685,6 +865,8 @@ function DashboardView() {
|
||||
}
|
||||
|
||||
export default function Roadmap() {
|
||||
const [projects, setProjects] = useKV<RoadmapProject[]>('roadmap-projects', [])
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
@@ -692,8 +874,12 @@ export default function Roadmap() {
|
||||
<p className="text-muted-foreground mt-1">Hoshin Kanri planning and execution tracking</p>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="dashboard" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-5 h-14 bg-muted/50">
|
||||
<Tabs defaultValue="projects" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-6 h-14 bg-muted/50">
|
||||
<TabsTrigger value="projects" className="gap-2 text-base font-semibold data-[state=active]:bg-primary data-[state=active]:text-primary-foreground">
|
||||
<FolderOpen size={20} weight="bold" />
|
||||
Projects
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="dashboard" className="gap-2 text-base font-semibold data-[state=active]:bg-primary data-[state=active]:text-primary-foreground">
|
||||
<Gauge size={20} weight="bold" />
|
||||
Dashboard
|
||||
@@ -716,16 +902,20 @@ export default function Roadmap() {
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="projects" className="mt-6">
|
||||
<ProjectsView projects={projects || []} setProjects={setProjects} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="dashboard" className="mt-6">
|
||||
<DashboardView />
|
||||
<DashboardView projects={projects || []} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="objectives" className="mt-6">
|
||||
<ObjectivesView />
|
||||
<ObjectivesView projects={projects || []} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="metrics" className="mt-6">
|
||||
<MetricsView />
|
||||
<MetricsView projects={projects || []} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="bowling" className="mt-6">
|
||||
|
||||
@@ -46,3 +46,52 @@ export interface Portfolio {
|
||||
capacity: number
|
||||
utilized: number
|
||||
}
|
||||
|
||||
export interface RoadmapProject {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
owner: string
|
||||
status: StatusType
|
||||
priority: PriorityType
|
||||
startDate: string
|
||||
endDate: string
|
||||
progress: number
|
||||
objectives: RoadmapObjective[]
|
||||
metrics: RoadmapMetric[]
|
||||
}
|
||||
|
||||
export interface RoadmapObjective {
|
||||
id: string
|
||||
projectId: string
|
||||
category: 'breakthrough' | 'annual' | 'improvement'
|
||||
description: string
|
||||
owner: string
|
||||
targetDate: string
|
||||
status: StatusType
|
||||
metrics: RoadmapMetric[]
|
||||
}
|
||||
|
||||
export interface RoadmapMetric {
|
||||
id: string
|
||||
name: string
|
||||
baseline: number
|
||||
current: number
|
||||
target: number
|
||||
unit: string
|
||||
frequency: 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'annual'
|
||||
lastUpdated: string
|
||||
trend: 'improving' | 'stable' | 'declining'
|
||||
}
|
||||
|
||||
export interface BowlingChartData {
|
||||
objective: string
|
||||
months: MonthStatus[]
|
||||
}
|
||||
|
||||
export interface MonthStatus {
|
||||
month: string
|
||||
status: 'green' | 'yellow' | 'red' | 'not-started'
|
||||
actual: number
|
||||
target: number
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user