From 884bbb11fc16bd7ce545a1c3f277fd95557b92e0 Mon Sep 17 00:00:00 2001 From: christiankrag Date: Sun, 14 Dec 2025 19:54:05 +0100 Subject: [PATCH] Add design thinking components for project management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add PhaseNavigator with 5 design phases (Empathize, Define, Ideate, Prototype, Test) - Add HealthWidget with circular progress and metrics - Add BacklogBoard with WSJF prioritization display - Add RecommendationsWidget for AI-powered insights - Add Tooltip component (shadcn/ui) - Add design-thinking types with phase configs and backlog item structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- package-lock.json | 155 +++++++++++ package.json | 1 + src/components/project/backlog-board.tsx | 249 ++++++++++++++++++ src/components/project/health-widget.tsx | 122 +++++++++ src/components/project/index.ts | 4 + src/components/project/phase-navigator.tsx | 167 ++++++++++++ .../project/recommendations-widget.tsx | 155 +++++++++++ src/components/ui/tooltip.tsx | 31 +++ src/types/design-thinking.ts | 123 +++++++++ 9 files changed, 1007 insertions(+) create mode 100644 src/components/project/backlog-board.tsx create mode 100644 src/components/project/health-widget.tsx create mode 100644 src/components/project/index.ts create mode 100644 src/components/project/phase-navigator.tsx create mode 100644 src/components/project/recommendations-widget.tsx create mode 100644 src/components/ui/tooltip.tsx create mode 100644 src/types/design-thinking.ts diff --git a/package-lock.json b/package-lock.json index 7c973f6..4671801 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", "@stripe/stripe-js": "^8.5.3", "@supabase/ssr": "^0.8.0", "@supabase/supabase-js": "^2.87.1", @@ -2471,6 +2472,96 @@ } } }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", @@ -2625,6 +2716,70 @@ } } }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/rect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", diff --git a/package.json b/package.json index fd287ff..2798fc7 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", "@stripe/stripe-js": "^8.5.3", "@supabase/ssr": "^0.8.0", "@supabase/supabase-js": "^2.87.1", diff --git a/src/components/project/backlog-board.tsx b/src/components/project/backlog-board.tsx new file mode 100644 index 0000000..cc22e44 --- /dev/null +++ b/src/components/project/backlog-board.tsx @@ -0,0 +1,249 @@ +'use client' + +import { useState } from 'react' +import { motion, AnimatePresence } from 'motion/react' +import { cn } from '@/lib/utils' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { + ArrowUp, + ArrowDown, + Clock, + Target, + AlertTriangle, + Zap, + MoreVertical, + GripVertical +} from 'lucide-react' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { designPhases, type BacklogItem, type DesignPhase } from '@/types/design-thinking' + +interface BacklogBoardProps { + items: BacklogItem[] + onItemClick?: (item: BacklogItem) => void + onStatusChange?: (itemId: string, newStatus: BacklogItem['status']) => void + onPriorityChange?: (itemId: string, direction: 'up' | 'down') => void + className?: string +} + +const statusColors: Record = { + backlog: 'bg-muted text-muted-foreground', + ready: 'bg-blue-500/10 text-blue-500 border-blue-500/20', + in_progress: 'bg-amber-500/10 text-amber-500 border-amber-500/20', + done: 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20', + blocked: 'bg-red-500/10 text-red-500 border-red-500/20' +} + +function PriorityScore({ score }: { score: number }) { + const getColor = (s: number) => { + if (s >= 7) return 'text-emerald-500' + if (s >= 4) return 'text-amber-500' + return 'text-muted-foreground' + } + + return ( +
+ + {score.toFixed(1)} +
+ ) +} + +function BacklogCard({ + item, + onItemClick, + onStatusChange, + onPriorityChange +}: { + item: BacklogItem + onItemClick?: (item: BacklogItem) => void + onStatusChange?: (itemId: string, newStatus: BacklogItem['status']) => void + onPriorityChange?: (itemId: string, direction: 'up' | 'down') => void +}) { + const phase = designPhases.find(p => p.id === item.phase) + + return ( + + onItemClick?.(item)} + > + +
+ +
+
+ {item.title} + + + + + + { e.stopPropagation(); onStatusChange?.(item.id, 'ready') }}> + Mark Ready + + { e.stopPropagation(); onStatusChange?.(item.id, 'in_progress') }}> + Start Work + + { e.stopPropagation(); onStatusChange?.(item.id, 'done') }}> + Mark Done + + { e.stopPropagation(); onStatusChange?.(item.id, 'blocked') }}> + Mark Blocked + + + +
+ +

+ {item.description} +

+ +
+
+ + {item.status.replace('_', ' ')} + + {phase && ( + {phase.icon} + )} +
+
+ +
+ + +
+
+
+ + {/* WSJF breakdown on hover */} +
+
+
+ + {item.user_value} +
+
+ + {item.time_criticality} +
+
+ + {item.risk_reduction} +
+
+ + E:{item.effort} +
+
+
+
+
+
+
+
+ ) +} + +export function BacklogBoard({ + items, + onItemClick, + onStatusChange, + onPriorityChange, + className +}: BacklogBoardProps) { + const [filter, setFilter] = useState('all') + + const filteredItems = filter === 'all' + ? items + : items.filter(item => item.status === filter) + + const sortedItems = [...filteredItems].sort((a, b) => b.priority_score - a.priority_score) + + const statusCounts = { + backlog: items.filter(i => i.status === 'backlog').length, + ready: items.filter(i => i.status === 'ready').length, + in_progress: items.filter(i => i.status === 'in_progress').length, + done: items.filter(i => i.status === 'done').length, + blocked: items.filter(i => i.status === 'blocked').length, + } + + return ( + + +
+ Backlog +
+ {(['all', 'backlog', 'ready', 'in_progress', 'done', 'blocked'] as const).map((status) => ( + + ))} +
+
+
+ + + {sortedItems.map((item) => ( + + ))} + + {sortedItems.length === 0 && ( +
+ No items in {filter === 'all' ? 'backlog' : filter} +
+ )} +
+
+ ) +} diff --git a/src/components/project/health-widget.tsx b/src/components/project/health-widget.tsx new file mode 100644 index 0000000..dd13745 --- /dev/null +++ b/src/components/project/health-widget.tsx @@ -0,0 +1,122 @@ +'use client' + +import { motion } from 'motion/react' +import { cn } from '@/lib/utils' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Activity, AlertTriangle, CheckCircle2, Clock, TrendingUp } from 'lucide-react' +import type { ProjectHealth } from '@/types/design-thinking' + +interface HealthWidgetProps { + health: ProjectHealth + className?: string +} + +function HealthRing({ value, size = 120, strokeWidth = 8 }: { value: number; size?: number; strokeWidth?: number }) { + const radius = (size - strokeWidth) / 2 + const circumference = radius * 2 * Math.PI + const offset = circumference - (value / 100) * circumference + + const getColor = (v: number) => { + if (v >= 80) return 'stroke-emerald-500' + if (v >= 60) return 'stroke-amber-500' + return 'stroke-red-500' + } + + return ( +
+ + + + +
+ + {value} + + Health Score +
+
+ ) +} + +export function HealthWidget({ health, className }: HealthWidgetProps) { + const metrics = [ + { + label: 'Velocity', + value: health.velocity, + icon: TrendingUp, + color: health.velocity >= 80 ? 'text-emerald-500' : health.velocity >= 60 ? 'text-amber-500' : 'text-red-500' + }, + { + label: 'Completion', + value: health.completion_rate, + icon: CheckCircle2, + color: health.completion_rate >= 80 ? 'text-emerald-500' : health.completion_rate >= 60 ? 'text-amber-500' : 'text-red-500', + suffix: '%' + }, + { + label: 'Blockers', + value: health.blockers, + icon: AlertTriangle, + color: health.blockers === 0 ? 'text-emerald-500' : health.blockers <= 2 ? 'text-amber-500' : 'text-red-500', + inverse: true + }, + { + label: 'Overdue', + value: health.overdue, + icon: Clock, + color: health.overdue === 0 ? 'text-emerald-500' : health.overdue <= 2 ? 'text-amber-500' : 'text-red-500', + inverse: true + } + ] + + return ( + + + + + Project Health + + + +
+ +
+ {metrics.map((metric) => ( +
+
+ + {metric.label} +
+

+ {metric.value}{metric.suffix || ''} +

+
+ ))} +
+
+
+
+ ) +} diff --git a/src/components/project/index.ts b/src/components/project/index.ts new file mode 100644 index 0000000..27ecde2 --- /dev/null +++ b/src/components/project/index.ts @@ -0,0 +1,4 @@ +export { PhaseNavigator } from './phase-navigator' +export { HealthWidget } from './health-widget' +export { BacklogBoard } from './backlog-board' +export { RecommendationsWidget } from './recommendations-widget' diff --git a/src/components/project/phase-navigator.tsx b/src/components/project/phase-navigator.tsx new file mode 100644 index 0000000..567f5a1 --- /dev/null +++ b/src/components/project/phase-navigator.tsx @@ -0,0 +1,167 @@ +'use client' + +import { motion } from 'motion/react' +import { cn } from '@/lib/utils' +import { Check, Loader2, AlertCircle, RotateCcw } from 'lucide-react' +import { DESIGN_PHASES, designPhases, type DesignPhase, type PhaseStatus } from '@/types/design-thinking' +import { Button } from '@/components/ui/button' +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip' + +interface PhaseNavigatorProps { + currentPhase: DesignPhase + phaseStatuses: Record + onPhaseClick?: (phase: DesignPhase) => void + onLoopBack?: (fromPhase: DesignPhase, toPhase: DesignPhase) => void + className?: string + variant?: 'horizontal' | 'compact' +} + +const colorMap: Record = { + purple: { bg: 'bg-purple-500/10', border: 'border-purple-500/50', text: 'text-purple-500', ring: 'ring-purple-500/30' }, + blue: { bg: 'bg-blue-500/10', border: 'border-blue-500/50', text: 'text-blue-500', ring: 'ring-blue-500/30' }, + amber: { bg: 'bg-amber-500/10', border: 'border-amber-500/50', text: 'text-amber-500', ring: 'ring-amber-500/30' }, + orange: { bg: 'bg-orange-500/10', border: 'border-orange-500/50', text: 'text-orange-500', ring: 'ring-orange-500/30' }, + emerald: { bg: 'bg-emerald-500/10', border: 'border-emerald-500/50', text: 'text-emerald-500', ring: 'ring-emerald-500/30' }, +} + +export function PhaseNavigator({ + currentPhase, + phaseStatuses, + onPhaseClick, + onLoopBack, + className, + variant = 'horizontal' +}: PhaseNavigatorProps) { + const currentIndex = DESIGN_PHASES.indexOf(currentPhase) + const progress = ((currentIndex + 1) / DESIGN_PHASES.length) * 100 + + const getStatusIcon = (status: PhaseStatus) => { + switch (status) { + case 'completed': + return + case 'in_progress': + return + case 'blocked': + case 'needs_review': + return + default: + return null + } + } + + return ( + +
+ {/* Progress bar */} +
+ +
+ + {/* Phase nodes */} +
+ {designPhases.map((phase, index) => { + const status = phaseStatuses[phase.id] + const isCurrent = phase.id === currentPhase + const isCompleted = status === 'completed' + const isBlocked = status === 'blocked' || status === 'needs_review' + const colors = colorMap[phase.color] + + return ( +
+ {/* Connector line */} + {index > 0 && ( +
+
+
+ )} + + {/* Phase node */} + + + onPhaseClick?.(phase.id)} + className={cn( + 'relative w-12 h-12 rounded-full flex items-center justify-center text-xl transition-all duration-300 border-2', + isCurrent && `${colors.bg} ${colors.border} ring-4 ${colors.ring}`, + isCompleted && 'bg-emerald-500/10 border-emerald-500/50', + isBlocked && 'bg-red-500/10 border-red-500/50', + !isCurrent && !isCompleted && !isBlocked && 'bg-muted border-transparent' + )} + whileHover={{ scale: 1.1 }} + whileTap={{ scale: 0.95 }} + animate={isCurrent ? { scale: [1, 1.05, 1] } : {}} + transition={{ repeat: isCurrent ? Infinity : 0, duration: 2 }} + > + {phase.icon} + {/* Status indicator */} + {(isCompleted || status === 'in_progress' || isBlocked) && ( +
+ {getStatusIcon(status)} +
+ )} +
+
+ +
+

{phase.label}

+

{phase.fullDescription}

+
+ {phase.commands.map(cmd => ( + {cmd} + ))} +
+
+
+
+ + {/* Labels */} + + {phase.label} + + + {phase.shortDescription} + +
+ ) + })} +
+ + {/* Loop back indicator */} + {onLoopBack && currentIndex > 0 && ( +
+ +
+ )} +
+ + ) +} diff --git a/src/components/project/recommendations-widget.tsx b/src/components/project/recommendations-widget.tsx new file mode 100644 index 0000000..04a6232 --- /dev/null +++ b/src/components/project/recommendations-widget.tsx @@ -0,0 +1,155 @@ +'use client' + +import { motion, AnimatePresence } from 'motion/react' +import { cn } from '@/lib/utils' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { + Sparkles, + Lightbulb, + AlertTriangle, + TrendingUp, + Zap, + ChevronRight, + X +} from 'lucide-react' +import type { AIRecommendation } from '@/types/design-thinking' + +interface RecommendationsWidgetProps { + recommendations: AIRecommendation[] + onAction?: (recommendation: AIRecommendation) => void + onDismiss?: (recommendationId: string) => void + className?: string +} + +const typeConfig: Record = { + action: { icon: Zap, color: 'text-blue-500', bg: 'bg-blue-500/10' }, + warning: { icon: AlertTriangle, color: 'text-amber-500', bg: 'bg-amber-500/10' }, + insight: { icon: Lightbulb, color: 'text-purple-500', bg: 'bg-purple-500/10' }, + optimization: { icon: TrendingUp, color: 'text-emerald-500', bg: 'bg-emerald-500/10' }, +} + +const priorityColors: Record = { + low: 'bg-muted text-muted-foreground', + medium: 'bg-amber-500/10 text-amber-600 border-amber-500/20', + high: 'bg-red-500/10 text-red-600 border-red-500/20', +} + +function RecommendationCard({ + recommendation, + onAction, + onDismiss +}: { + recommendation: AIRecommendation + onAction?: (recommendation: AIRecommendation) => void + onDismiss?: (recommendationId: string) => void +}) { + const config = typeConfig[recommendation.type] + const Icon = config.icon + + return ( + +
+ {/* Dismiss button */} + {onDismiss && ( + + )} + +
+
+ +
+
+
+ {recommendation.title} + + {recommendation.priority} + +
+

+ {recommendation.description} +

+ {recommendation.action_command && ( + + )} +
+
+
+
+ ) +} + +export function RecommendationsWidget({ + recommendations, + onAction, + onDismiss, + className +}: RecommendationsWidgetProps) { + const sortedRecommendations = [...recommendations].sort((a, b) => { + const priorityOrder = { high: 0, medium: 1, low: 2 } + return priorityOrder[a.priority] - priorityOrder[b.priority] + }) + + return ( + + + + + AI Recommendations + {recommendations.length > 0 && ( + + {recommendations.length} + + )} + + + + + {sortedRecommendations.map((rec) => ( + + ))} + + {recommendations.length === 0 && ( +
+ +

+ No recommendations yet +

+

+ AI insights will appear as you work +

+
+ )} +
+
+ ) +} diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..a011d30 --- /dev/null +++ b/src/components/ui/tooltip.tsx @@ -0,0 +1,31 @@ +'use client' + +import * as React from 'react' +import * as TooltipPrimitive from '@radix-ui/react-tooltip' +import { cn } from '@/lib/utils' + +const TooltipProvider = TooltipPrimitive.Provider + +const Tooltip = TooltipPrimitive.Root + +const TooltipTrigger = TooltipPrimitive.Trigger + +const TooltipContent = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +TooltipContent.displayName = TooltipPrimitive.Content.displayName + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/src/types/design-thinking.ts b/src/types/design-thinking.ts new file mode 100644 index 0000000..92c8648 --- /dev/null +++ b/src/types/design-thinking.ts @@ -0,0 +1,123 @@ +export const DESIGN_PHASES = ['empathize', 'define', 'ideate', 'prototype', 'test'] as const +export type DesignPhase = typeof DESIGN_PHASES[number] + +export type PhaseStatus = 'not_started' | 'in_progress' | 'completed' | 'blocked' | 'needs_review' + +export interface PhaseConfig { + id: DesignPhase + icon: string + label: string + shortDescription: string + fullDescription: string + commands: string[] + color: string + questions: string[] +} + +export const designPhases: PhaseConfig[] = [ + { + id: 'empathize', + icon: '🎯', + label: 'Understand', + shortDescription: 'Learn about your users', + fullDescription: 'Research user needs, pain points, and behaviors', + commands: ['/research', '/ux', '/interview'], + color: 'purple', + questions: [ + 'Who are your users?', + 'What problems do they face?', + 'What do they need?' + ] + }, + { + id: 'define', + icon: '📋', + label: 'Focus', + shortDescription: 'Define the problem', + fullDescription: 'Create clear problem statements and success criteria', + commands: ['/plan', '/roadmap', '/goal'], + color: 'blue', + questions: [ + 'What specific problem are we solving?', + 'How will we measure success?', + 'What are our constraints?' + ] + }, + { + id: 'ideate', + icon: '💡', + label: 'Explore', + shortDescription: 'Generate solutions', + fullDescription: 'Brainstorm ideas and evaluate options', + commands: ['/brainstorm', '/ideate', '/options'], + color: 'amber', + questions: [ + 'What are all possible solutions?', + 'What are the trade-offs?', + 'Which ideas should we explore?' + ] + }, + { + id: 'prototype', + icon: '🔨', + label: 'Build', + shortDescription: 'Create & deploy', + fullDescription: 'Build working prototypes and ship features', + commands: ['/build', '/deploy', '/ui'], + color: 'orange', + questions: [ + 'What should we build first?', + 'How can we test quickly?', + 'What is the minimum viable feature?' + ] + }, + { + id: 'test', + icon: '✅', + label: 'Validate', + shortDescription: 'Test & review', + fullDescription: 'Validate with users and gather feedback', + commands: ['/test', '/review', '/feedback'], + color: 'emerald', + questions: [ + 'Does this solve the problem?', + 'What feedback do users have?', + 'What needs improvement?' + ] + } +] + +export interface BacklogItem { + id: string + title: string + description: string + phase: DesignPhase + priority_score: number + user_value: number + time_criticality: number + risk_reduction: number + effort: number + status: 'backlog' | 'ready' | 'in_progress' | 'done' | 'blocked' + depends_on?: string[] + assigned_to?: string + created_at: string + updated_at: string +} + +export interface ProjectHealth { + overall: number + velocity: number + blockers: number + overdue: number + completion_rate: number +} + +export interface AIRecommendation { + id: string + type: 'action' | 'warning' | 'insight' | 'optimization' + title: string + description: string + priority: 'low' | 'medium' | 'high' + related_items?: string[] + action_command?: string +}