prjct-cli 0.13.3 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +122 -0
- package/bin/prjct +10 -13
- package/core/agentic/memory-system/semantic-memories.ts +2 -1
- package/core/agentic/plan-mode/plan-mode.ts +2 -1
- package/core/agentic/prompt-builder.ts +22 -43
- package/core/agentic/services.ts +5 -5
- package/core/agentic/smart-context.ts +7 -2
- package/core/command-registry/core-commands.ts +54 -29
- package/core/command-registry/optional-commands.ts +64 -0
- package/core/command-registry/setup-commands.ts +18 -3
- package/core/commands/analysis.ts +21 -68
- package/core/commands/analytics.ts +247 -213
- package/core/commands/base.ts +1 -1
- package/core/commands/index.ts +41 -36
- package/core/commands/maintenance.ts +300 -31
- package/core/commands/planning.ts +233 -22
- package/core/commands/setup.ts +3 -8
- package/core/commands/shipping.ts +14 -18
- package/core/commands/types.ts +8 -6
- package/core/commands/workflow.ts +105 -100
- package/core/context/generator.ts +317 -0
- package/core/context-sync.ts +7 -350
- package/core/data/index.ts +13 -32
- package/core/data/md-ideas-manager.ts +155 -0
- package/core/data/md-queue-manager.ts +4 -3
- package/core/data/md-shipped-manager.ts +90 -0
- package/core/data/md-state-manager.ts +11 -7
- package/core/domain/agent-generator.ts +23 -63
- package/core/events/index.ts +143 -0
- package/core/index.ts +17 -14
- package/core/infrastructure/capability-installer.ts +13 -149
- package/core/infrastructure/migrator/project-scanner.ts +2 -1
- package/core/infrastructure/path-manager.ts +4 -6
- package/core/infrastructure/setup.ts +3 -0
- package/core/infrastructure/uuid-migration.ts +750 -0
- package/core/outcomes/recorder.ts +2 -1
- package/core/plugin/loader.ts +4 -7
- package/core/plugin/registry.ts +3 -3
- package/core/schemas/index.ts +23 -25
- package/core/schemas/state.ts +1 -0
- package/core/serializers/ideas-serializer.ts +187 -0
- package/core/serializers/index.ts +16 -0
- package/core/serializers/shipped-serializer.ts +108 -0
- package/core/session/utils.ts +3 -9
- package/core/storage/ideas-storage.ts +273 -0
- package/core/storage/index.ts +204 -0
- package/core/storage/queue-storage.ts +297 -0
- package/core/storage/shipped-storage.ts +223 -0
- package/core/storage/state-storage.ts +235 -0
- package/core/storage/storage-manager.ts +175 -0
- package/package.json +1 -1
- package/packages/web/app/api/projects/[id]/momentum/route.ts +257 -0
- package/packages/web/app/api/sessions/current/route.ts +132 -0
- package/packages/web/app/api/sessions/history/route.ts +96 -14
- package/packages/web/app/globals.css +5 -0
- package/packages/web/app/layout.tsx +2 -0
- package/packages/web/app/project/[id]/code/layout.tsx +18 -0
- package/packages/web/app/project/[id]/code/page.tsx +408 -0
- package/packages/web/app/project/[id]/page.tsx +359 -389
- package/packages/web/app/project/[id]/reports/page.tsx +59 -0
- package/packages/web/app/project/[id]/reports/print/page.tsx +58 -0
- package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -1
- package/packages/web/components/AgentsCard/AgentsCard.tsx +64 -34
- package/packages/web/components/AgentsCard/AgentsCard.types.ts +1 -0
- package/packages/web/components/AppSidebar/AppSidebar.tsx +135 -11
- package/packages/web/components/BentoCard/BentoCard.constants.ts +3 -3
- package/packages/web/components/BentoCard/BentoCard.tsx +2 -1
- package/packages/web/components/BentoGrid/BentoGrid.tsx +2 -2
- package/packages/web/components/BlockersCard/BlockersCard.tsx +65 -57
- package/packages/web/components/BlockersCard/BlockersCard.types.ts +1 -0
- package/packages/web/components/CommandBar/CommandBar.tsx +67 -0
- package/packages/web/components/CommandBar/index.ts +1 -0
- package/packages/web/components/DashboardContent/DashboardContent.tsx +35 -5
- package/packages/web/components/DateGroup/DateGroup.tsx +1 -1
- package/packages/web/components/EmptyState/EmptyState.tsx +39 -21
- package/packages/web/components/EmptyState/EmptyState.types.ts +1 -0
- package/packages/web/components/EventRow/EventRow.tsx +4 -4
- package/packages/web/components/EventRow/EventRow.utils.ts +3 -3
- package/packages/web/components/HeroSection/HeroSection.tsx +52 -15
- package/packages/web/components/HeroSection/HeroSection.types.ts +4 -4
- package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -3
- package/packages/web/components/IdeasCard/IdeasCard.tsx +94 -27
- package/packages/web/components/IdeasCard/IdeasCard.types.ts +1 -0
- package/packages/web/components/MasonryGrid/MasonryGrid.tsx +18 -0
- package/packages/web/components/MasonryGrid/index.ts +1 -0
- package/packages/web/components/MomentumWidget/MomentumWidget.tsx +119 -0
- package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +16 -0
- package/packages/web/components/MomentumWidget/index.ts +2 -0
- package/packages/web/components/NowCard/NowCard.tsx +81 -56
- package/packages/web/components/NowCard/NowCard.types.ts +1 -0
- package/packages/web/components/PageHeader/PageHeader.tsx +24 -0
- package/packages/web/components/PageHeader/index.ts +1 -0
- package/packages/web/components/ProgressRing/ProgressRing.constants.ts +2 -2
- package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +2 -2
- package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +37 -0
- package/packages/web/components/ProjectColorDot/index.ts +1 -0
- package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +104 -0
- package/packages/web/components/ProjectSelectorModal/index.ts +1 -0
- package/packages/web/components/Providers/Providers.tsx +4 -1
- package/packages/web/components/QueueCard/QueueCard.tsx +78 -25
- package/packages/web/components/QueueCard/QueueCard.types.ts +1 -0
- package/packages/web/components/QueueCard/QueueCard.utils.ts +3 -3
- package/packages/web/components/RecoverCard/RecoverCard.tsx +72 -0
- package/packages/web/components/RecoverCard/RecoverCard.types.ts +16 -0
- package/packages/web/components/RecoverCard/index.ts +2 -0
- package/packages/web/components/RoadmapCard/RoadmapCard.tsx +101 -33
- package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +1 -0
- package/packages/web/components/ShipsCard/ShipsCard.tsx +71 -28
- package/packages/web/components/ShipsCard/ShipsCard.types.ts +2 -0
- package/packages/web/components/SparklineChart/SparklineChart.tsx +20 -18
- package/packages/web/components/StatsMasonry/StatsMasonry.tsx +95 -0
- package/packages/web/components/StatsMasonry/index.ts +1 -0
- package/packages/web/components/StreakCard/StreakCard.tsx +37 -35
- package/packages/web/components/TasksCounter/TasksCounter.tsx +1 -1
- package/packages/web/components/TechStackBadges/TechStackBadges.tsx +12 -4
- package/packages/web/components/TerminalDock/DockToggleTab.tsx +29 -0
- package/packages/web/components/TerminalDock/TerminalDock.tsx +386 -0
- package/packages/web/components/TerminalDock/TerminalDockTab.tsx +130 -0
- package/packages/web/components/TerminalDock/TerminalTabBar.tsx +142 -0
- package/packages/web/components/TerminalDock/index.ts +2 -0
- package/packages/web/components/VelocityBadge/VelocityBadge.tsx +8 -3
- package/packages/web/components/VelocityCard/VelocityCard.tsx +49 -47
- package/packages/web/components/WeeklyReports/PrintableReport.tsx +259 -0
- package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +187 -0
- package/packages/web/components/WeeklyReports/WeekCalendar.tsx +288 -0
- package/packages/web/components/WeeklyReports/WeeklyReports.tsx +149 -0
- package/packages/web/components/WeeklyReports/index.ts +4 -0
- package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +16 -4
- package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +1 -0
- package/packages/web/components/charts/SessionsChart.tsx +6 -3
- package/packages/web/components/ui/dialog.tsx +143 -0
- package/packages/web/components/ui/drawer.tsx +135 -0
- package/packages/web/components/ui/select.tsx +187 -0
- package/packages/web/context/GlobalTerminalContext.tsx +538 -0
- package/packages/web/lib/commands.ts +81 -0
- package/packages/web/lib/generate-week-report.ts +285 -0
- package/packages/web/lib/parse-prjct-files.ts +56 -55
- package/packages/web/lib/project-colors.ts +58 -0
- package/packages/web/lib/projects.ts +58 -5
- package/packages/web/lib/services/projects.server.ts +11 -1
- package/packages/web/next-env.d.ts +1 -1
- package/packages/web/package.json +5 -1
- package/templates/commands/analyze.md +39 -3
- package/templates/commands/ask.md +58 -3
- package/templates/commands/bug.md +117 -26
- package/templates/commands/dash.md +95 -158
- package/templates/commands/done.md +130 -148
- package/templates/commands/feature.md +125 -103
- package/templates/commands/git.md +18 -3
- package/templates/commands/idea.md +121 -38
- package/templates/commands/init.md +124 -20
- package/templates/commands/migrate-all.md +63 -28
- package/templates/commands/migrate.md +140 -0
- package/templates/commands/next.md +115 -5
- package/templates/commands/now.md +146 -82
- package/templates/commands/pause.md +89 -74
- package/templates/commands/redo.md +6 -4
- package/templates/commands/resume.md +141 -59
- package/templates/commands/setup.md +18 -3
- package/templates/commands/ship.md +103 -231
- package/templates/commands/spec.md +98 -8
- package/templates/commands/suggest.md +22 -2
- package/templates/commands/sync.md +192 -203
- package/templates/commands/undo.md +6 -4
- package/templates/mcp-config.json +20 -1
- package/core/data/agents-manager.ts +0 -76
- package/core/data/analysis-manager.ts +0 -83
- package/core/data/base-manager.ts +0 -156
- package/core/data/ideas-manager.ts +0 -81
- package/core/data/outcomes-manager.ts +0 -96
- package/core/data/project-manager.ts +0 -75
- package/core/data/roadmap-manager.ts +0 -118
- package/core/data/shipped-manager.ts +0 -65
- package/core/data/state-manager.ts +0 -214
- package/core/state/index.ts +0 -25
- package/core/state/manager.ts +0 -376
- package/core/state/types.ts +0 -185
- package/core/utils/project-capabilities.ts +0 -156
- package/core/view-generator.ts +0 -536
- package/packages/web/app/project/[id]/stats/loading.tsx +0 -43
- package/packages/web/app/project/[id]/stats/page.tsx +0 -253
- package/templates/agent-assignment.md +0 -72
- package/templates/analysis/project-analysis.md +0 -78
- package/templates/checklists/accessibility.md +0 -33
- package/templates/commands/build.md +0 -17
- package/templates/commands/decision.md +0 -226
- package/templates/commands/fix.md +0 -79
- package/templates/commands/help.md +0 -61
- package/templates/commands/progress.md +0 -14
- package/templates/commands/recap.md +0 -14
- package/templates/commands/roadmap.md +0 -52
- package/templates/commands/status.md +0 -17
- package/templates/commands/task.md +0 -63
- package/templates/commands/work.md +0 -44
- package/templates/commands/workflow.md +0 -12
|
@@ -1,414 +1,384 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { useState, use } from 'react'
|
|
4
|
-
import { useRouter } from 'next/navigation'
|
|
5
|
-
import { useProject, useDeleteProject } from '@/hooks/useProjects'
|
|
6
|
-
import { TerminalTabsProvider, useTerminalTabs } from '@/context/TerminalTabsContext'
|
|
7
|
-
import { TerminalTabs } from '@/components/TerminalTabs'
|
|
8
|
-
import { Button } from '@/components/ui/button'
|
|
9
|
-
import { Badge } from '@/components/ui/badge'
|
|
10
|
-
import { TooltipProvider } from '@/components/ui/tooltip'
|
|
11
|
-
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'
|
|
1
|
+
import { notFound } from 'next/navigation'
|
|
2
|
+
import type { Metadata } from 'next'
|
|
12
3
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from '@/
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
Trash2,
|
|
37
|
-
ArrowLeft,
|
|
38
|
-
FolderGit2,
|
|
39
|
-
Pause,
|
|
40
|
-
BarChart3,
|
|
41
|
-
TrendingUp,
|
|
42
|
-
Activity,
|
|
43
|
-
History,
|
|
44
|
-
Undo2,
|
|
45
|
-
Redo2,
|
|
46
|
-
Command,
|
|
47
|
-
X,
|
|
48
|
-
RefreshCw
|
|
49
|
-
} from 'lucide-react'
|
|
50
|
-
import { cn } from '@/lib/utils'
|
|
51
|
-
|
|
52
|
-
// Commands ordered by real developer workflow
|
|
53
|
-
const WORKFLOW_COMMANDS = [
|
|
54
|
-
{ cmd: 'p. now', icon: Target, tip: 'Set task', group: 'work' },
|
|
55
|
-
{ cmd: 'p. done', icon: CheckCircle2, tip: 'Complete', group: 'work' },
|
|
56
|
-
{ cmd: 'p. pause', icon: Pause, tip: 'Pause', group: 'session' },
|
|
57
|
-
{ cmd: 'p. resume', icon: Play, tip: 'Resume', group: 'session' },
|
|
58
|
-
{ cmd: 'p. feature', icon: Sparkles, tip: 'Feature', group: 'plan' },
|
|
59
|
-
{ cmd: 'p. idea', icon: Lightbulb, tip: 'Idea', group: 'plan' },
|
|
60
|
-
{ cmd: 'p. next', icon: ListTodo, tip: 'Queue', group: 'plan' },
|
|
61
|
-
{ cmd: 'p. ship', icon: Rocket, tip: 'Ship', group: 'ship' },
|
|
62
|
-
{ cmd: 'p. recap', icon: BarChart3, tip: 'Recap', group: 'status' },
|
|
63
|
-
{ cmd: 'p. progress', icon: TrendingUp, tip: 'Progress', group: 'status' },
|
|
64
|
-
{ cmd: 'p. status', icon: Activity, tip: 'Status', group: 'status' },
|
|
65
|
-
{ cmd: 'p. history', icon: History, tip: 'History', group: 'status' },
|
|
66
|
-
{ cmd: 'p. undo', icon: Undo2, tip: 'Undo', group: 'recovery' },
|
|
67
|
-
{ cmd: 'p. redo', icon: Redo2, tip: 'Redo', group: 'recovery' },
|
|
68
|
-
] as const
|
|
69
|
-
|
|
70
|
-
const COMMAND_GROUPS = ['work', 'session', 'plan', 'ship', 'status', 'recovery'] as const
|
|
71
|
-
|
|
72
|
-
// Command sidebar content - shared between desktop and mobile
|
|
73
|
-
function CommandSidebarContent({
|
|
74
|
-
projectId,
|
|
75
|
-
project,
|
|
76
|
-
isActiveConnected,
|
|
77
|
-
sendCommandToActive,
|
|
78
|
-
onCommandClick
|
|
79
|
-
}: {
|
|
80
|
-
projectId: string
|
|
81
|
-
project: { name?: string; iconPath?: string | null }
|
|
82
|
-
isActiveConnected: boolean
|
|
83
|
-
sendCommandToActive: (cmd: string) => void
|
|
84
|
-
onCommandClick?: () => void
|
|
85
|
-
}) {
|
|
86
|
-
const router = useRouter()
|
|
87
|
-
|
|
88
|
-
const handleCommand = (cmd: string) => {
|
|
89
|
-
sendCommandToActive(cmd)
|
|
90
|
-
onCommandClick?.()
|
|
4
|
+
getStats,
|
|
5
|
+
getInsightMessage,
|
|
6
|
+
calculateStreak,
|
|
7
|
+
getVelocityChange,
|
|
8
|
+
getWeeklyVelocityData,
|
|
9
|
+
type StatsResult
|
|
10
|
+
} from '@/lib/services/stats.server'
|
|
11
|
+
import { getProject } from '@/lib/services/projects.server'
|
|
12
|
+
import { getProjectEmoji } from '@/lib/project-colors'
|
|
13
|
+
import type { TimelineEvent } from '@/lib/parse-prjct-files'
|
|
14
|
+
|
|
15
|
+
import { HeroSection } from '@/components/HeroSection'
|
|
16
|
+
import { StatsMasonry } from '@/components/StatsMasonry'
|
|
17
|
+
|
|
18
|
+
// Dynamic metadata for browser tab title
|
|
19
|
+
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
|
|
20
|
+
const { id: projectId } = await params
|
|
21
|
+
const project = await getProject(projectId)
|
|
22
|
+
const projectName = project?.name ?? projectId
|
|
23
|
+
const emoji = getProjectEmoji(projectId)
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
title: `${emoji} ${projectName} / p.`,
|
|
91
27
|
}
|
|
28
|
+
}
|
|
92
29
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
30
|
+
// Types for normalized component data
|
|
31
|
+
interface NormalizedCurrentTask {
|
|
32
|
+
task: string
|
|
33
|
+
startedAt?: string
|
|
34
|
+
agent?: string
|
|
35
|
+
estimatedDuration?: string
|
|
36
|
+
pausedAt?: string
|
|
37
|
+
pauseReason?: string
|
|
38
|
+
}
|
|
102
39
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
icon={BarChart3}
|
|
108
|
-
tip="Stats"
|
|
109
|
-
disabled={false}
|
|
110
|
-
onClick={() => {
|
|
111
|
-
router.push(`/project/${projectId}/stats`)
|
|
112
|
-
onCommandClick?.()
|
|
113
|
-
}}
|
|
114
|
-
/>
|
|
115
|
-
{/* Sync button - prominent, always visible */}
|
|
116
|
-
<CommandButton
|
|
117
|
-
cmd="p. sync"
|
|
118
|
-
icon={RefreshCw}
|
|
119
|
-
tip="Sync"
|
|
120
|
-
disabled={!isActiveConnected}
|
|
121
|
-
onClick={() => handleCommand('p. sync')}
|
|
122
|
-
variant="primary"
|
|
123
|
-
/>
|
|
124
|
-
<div className="border-b border-border w-8 my-2 mx-auto" />
|
|
125
|
-
|
|
126
|
-
{COMMAND_GROUPS.map((group, groupIndex) => (
|
|
127
|
-
<div key={group} className="flex flex-col items-center">
|
|
128
|
-
{WORKFLOW_COMMANDS.filter(c => c.group === group).map(({ cmd, icon, tip }) => (
|
|
129
|
-
<CommandButton
|
|
130
|
-
key={cmd}
|
|
131
|
-
cmd={cmd}
|
|
132
|
-
icon={icon}
|
|
133
|
-
tip={tip}
|
|
134
|
-
disabled={!isActiveConnected}
|
|
135
|
-
onClick={() => handleCommand(cmd)}
|
|
136
|
-
/>
|
|
137
|
-
))}
|
|
138
|
-
{groupIndex < COMMAND_GROUPS.length - 1 && (
|
|
139
|
-
<div className="border-b border-border w-8 my-2" />
|
|
140
|
-
)}
|
|
141
|
-
</div>
|
|
142
|
-
))}
|
|
143
|
-
</div>
|
|
144
|
-
</>
|
|
145
|
-
)
|
|
40
|
+
interface NormalizedQueueItem {
|
|
41
|
+
task: string
|
|
42
|
+
priority?: 'low' | 'medium' | 'high' | 'critical' | number
|
|
43
|
+
estimatedDuration?: string
|
|
146
44
|
}
|
|
147
45
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
46
|
+
interface NormalizedShip {
|
|
47
|
+
name: string
|
|
48
|
+
date: string
|
|
49
|
+
duration?: string
|
|
50
|
+
}
|
|
152
51
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
} = useTerminalTabs()
|
|
52
|
+
interface NormalizedIdea {
|
|
53
|
+
title: string
|
|
54
|
+
impact?: string
|
|
55
|
+
}
|
|
158
56
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
57
|
+
interface NormalizedAgent {
|
|
58
|
+
name: string
|
|
59
|
+
description?: string
|
|
60
|
+
successRate?: number
|
|
61
|
+
tasksCompleted?: number
|
|
62
|
+
bestFor?: string[]
|
|
63
|
+
}
|
|
162
64
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
project={project}
|
|
172
|
-
isActiveConnected={isActiveConnected}
|
|
173
|
-
sendCommandToActive={sendCommandToActive}
|
|
174
|
-
/>
|
|
175
|
-
</aside>
|
|
176
|
-
|
|
177
|
-
{/* Main */}
|
|
178
|
-
<main className="flex-1 flex flex-col min-w-0">
|
|
179
|
-
{/* Header - Responsive */}
|
|
180
|
-
<header className="h-auto md:h-14 flex flex-col md:flex-row md:items-center justify-between px-3 md:px-4 py-2 md:py-0 border-b border-border bg-card gap-2">
|
|
181
|
-
{/* Mobile: Add padding for hamburger menu */}
|
|
182
|
-
<div className="flex items-center gap-3 pl-12 md:pl-0">
|
|
183
|
-
{/* Mobile: Show project avatar */}
|
|
184
|
-
<div className="md:hidden">
|
|
185
|
-
<ProjectAvatar
|
|
186
|
-
projectId={projectId}
|
|
187
|
-
name={project.name || projectId}
|
|
188
|
-
iconPath={project.iconPath}
|
|
189
|
-
size="sm"
|
|
190
|
-
/>
|
|
191
|
-
</div>
|
|
192
|
-
<div className="flex flex-col min-w-0">
|
|
193
|
-
<div className="flex items-center gap-2">
|
|
194
|
-
<span className="font-bold leading-tight truncate">{project.name || projectId}</span>
|
|
195
|
-
{project.version && (
|
|
196
|
-
<Badge variant="outline" className="text-[10px] px-1.5 py-0 font-mono shrink-0">
|
|
197
|
-
v{project.version}
|
|
198
|
-
</Badge>
|
|
199
|
-
)}
|
|
200
|
-
</div>
|
|
201
|
-
{project.repoPath && (
|
|
202
|
-
<span className="text-xs text-muted-foreground leading-tight flex items-center gap-1 truncate">
|
|
203
|
-
<FolderGit2 className="w-3 h-3 shrink-0" />
|
|
204
|
-
<span className="truncate">{formatPath(project.repoPath)}</span>
|
|
205
|
-
</span>
|
|
206
|
-
)}
|
|
207
|
-
</div>
|
|
208
|
-
{hasActiveSessions && (
|
|
209
|
-
<Badge variant="outline" className="text-green-500 border-green-500/50 shrink-0">
|
|
210
|
-
{sessions.filter(s => s.isConnected).length} active
|
|
211
|
-
</Badge>
|
|
212
|
-
)}
|
|
213
|
-
</div>
|
|
214
|
-
|
|
215
|
-
{/* Desktop only: metadata and tech stack */}
|
|
216
|
-
<div className="hidden md:flex items-center gap-4">
|
|
217
|
-
<div className="flex items-center gap-3 text-xs text-muted-foreground">
|
|
218
|
-
{project.stack && <span>{project.stack}</span>}
|
|
219
|
-
{project.filesCount && (
|
|
220
|
-
<span><span className="font-medium text-foreground">{project.filesCount}</span> files</span>
|
|
221
|
-
)}
|
|
222
|
-
{project.commitsCount && (
|
|
223
|
-
<span><span className="font-medium text-foreground">{project.commitsCount}</span> commits</span>
|
|
224
|
-
)}
|
|
225
|
-
</div>
|
|
226
|
-
<TechStackBadges techStack={project.techStack || []} />
|
|
227
|
-
</div>
|
|
228
|
-
</header>
|
|
229
|
-
|
|
230
|
-
{/* Terminal tabs area */}
|
|
231
|
-
<div className="flex-1 min-h-0">
|
|
232
|
-
<TerminalTabs projectDir={project.repoPath || project.path || '/tmp'} />
|
|
233
|
-
</div>
|
|
234
|
-
</main>
|
|
235
|
-
</div>
|
|
65
|
+
interface NormalizedRoadmap {
|
|
66
|
+
phases: Array<{
|
|
67
|
+
name: string
|
|
68
|
+
progress: number
|
|
69
|
+
features?: Array<{ name: string; status: string }>
|
|
70
|
+
}>
|
|
71
|
+
progress: number
|
|
72
|
+
}
|
|
236
73
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
74
|
+
// Data normalization functions
|
|
75
|
+
function normalizeCurrentTask(stats: StatsResult): NormalizedCurrentTask | null {
|
|
76
|
+
if (stats.state?.currentTask) {
|
|
77
|
+
return {
|
|
78
|
+
task: stats.state.currentTask.description,
|
|
79
|
+
startedAt: stats.state.currentTask.startedAt,
|
|
80
|
+
// Simplified - removed legacy fields not in new schema
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return stats.legacyStats?.currentTask ?? null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalizeQueue(stats: StatsResult): NormalizedQueueItem[] {
|
|
87
|
+
if (stats.queue?.tasks) {
|
|
88
|
+
return stats.queue.tasks
|
|
89
|
+
.filter(t => !t.completed)
|
|
90
|
+
.map(q => ({
|
|
91
|
+
task: q.description,
|
|
92
|
+
priority: q.priority,
|
|
93
|
+
}))
|
|
94
|
+
}
|
|
95
|
+
return stats.legacyStats?.queue ?? []
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function normalizeRoadmap(stats: StatsResult): NormalizedRoadmap | null {
|
|
99
|
+
const features = stats.roadmap?.features ?? []
|
|
100
|
+
if (features.length > 0) {
|
|
101
|
+
const completed = features.filter(f =>
|
|
102
|
+
f.status === 'shipped' || f.status === 'completed'
|
|
103
|
+
).length
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
phases: features.map(f => ({
|
|
107
|
+
name: f.name,
|
|
108
|
+
progress: f.status === 'shipped' || f.status === 'completed' ? 100 :
|
|
109
|
+
f.status === 'active' ? 50 : 0,
|
|
110
|
+
features: f.tasks.map(t => ({
|
|
111
|
+
name: t.description,
|
|
112
|
+
status: t.completed ? 'completed' : 'pending'
|
|
113
|
+
}))
|
|
114
|
+
})),
|
|
115
|
+
progress: Math.round((completed / features.length) * 100)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return stats.legacyStats?.roadmap ?? null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizeShipped(stats: StatsResult): NormalizedShip[] {
|
|
122
|
+
// Try new format first
|
|
123
|
+
const items = stats.shipped?.items ?? []
|
|
124
|
+
if (items.length > 0) {
|
|
125
|
+
return items.map(s => ({
|
|
126
|
+
name: s.name,
|
|
127
|
+
date: s.shippedAt || s.date || new Date().toISOString(),
|
|
128
|
+
}))
|
|
129
|
+
}
|
|
130
|
+
// Fallback to legacy
|
|
131
|
+
return (stats.legacyStats?.shipped ?? []).map(s => ({
|
|
132
|
+
name: s.name,
|
|
133
|
+
date: s.date,
|
|
134
|
+
duration: s.time,
|
|
135
|
+
}))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function normalizeIdeas(stats: StatsResult): NormalizedIdea[] {
|
|
139
|
+
// Try new format first
|
|
140
|
+
const ideas = stats.ideas?.ideas ?? []
|
|
141
|
+
if (ideas.length > 0) {
|
|
142
|
+
return ideas
|
|
143
|
+
.filter(i => i.status === 'pending')
|
|
144
|
+
.map(i => ({
|
|
145
|
+
title: i.text,
|
|
146
|
+
impact: i.priority?.toUpperCase() || 'MEDIUM'
|
|
147
|
+
}))
|
|
148
|
+
}
|
|
149
|
+
// Fallback to legacy
|
|
150
|
+
return (stats.legacyStats?.ideas?.pending ?? []).map(i => ({
|
|
151
|
+
title: i.title,
|
|
152
|
+
impact: i.impact?.toUpperCase() || 'MEDIUM'
|
|
153
|
+
}))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function normalizeAgents(stats: StatsResult): NormalizedAgent[] {
|
|
157
|
+
// Try new format first
|
|
158
|
+
if (stats.agents.length > 0) {
|
|
159
|
+
return stats.agents.map(a => ({
|
|
160
|
+
name: a.name,
|
|
161
|
+
description: a.description,
|
|
162
|
+
successRate: a.successRate,
|
|
163
|
+
tasksCompleted: a.tasksCompleted,
|
|
164
|
+
bestFor: a.bestFor,
|
|
165
|
+
}))
|
|
166
|
+
}
|
|
167
|
+
// Fallback to legacy
|
|
168
|
+
return (stats.legacyStats?.agents ?? []).map(a => ({
|
|
169
|
+
name: a.name,
|
|
170
|
+
description: a.role,
|
|
171
|
+
bestFor: a.whenToUse,
|
|
172
|
+
}))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function normalizeTimeline(stats: StatsResult): TimelineEvent[] {
|
|
176
|
+
if (stats.metrics?.recentActivity?.length) {
|
|
177
|
+
return stats.metrics.recentActivity.map(a => ({
|
|
178
|
+
ts: a.timestamp,
|
|
179
|
+
type: a.action || a.type || 'task_completed',
|
|
180
|
+
task: a.description || '',
|
|
181
|
+
}))
|
|
182
|
+
}
|
|
183
|
+
return stats.legacyStats?.timeline ?? []
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function getVelocity(stats: StatsResult): number {
|
|
187
|
+
if (stats.metrics?.velocity?.tasksPerDay) {
|
|
188
|
+
return stats.metrics.velocity.tasksPerDay
|
|
189
|
+
}
|
|
190
|
+
return stats.legacyStats?.metrics?.velocity?.tasksPerDay ?? 0
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function getTotalShips(stats: StatsResult): number {
|
|
194
|
+
// Use shipped.md items count (legacyStats.shipped) as source of truth
|
|
195
|
+
return stats.shipped?.items?.length ?? stats.legacyStats?.shipped?.length ?? 0
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function getCompletionRate(stats: StatsResult): number {
|
|
199
|
+
const completed = stats.legacyStats?.shipped?.length ?? 0
|
|
200
|
+
const pending = stats.legacyStats?.queue?.length ?? 0
|
|
201
|
+
const total = completed + pending
|
|
202
|
+
if (total === 0) return 0
|
|
203
|
+
return Math.round((completed / total) * 100)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Calculate streak from legacy timeline
|
|
207
|
+
function calculateStreakFromTimeline(stats: StatsResult): number {
|
|
208
|
+
const timeline = stats.legacyStats?.timeline ?? []
|
|
209
|
+
if (timeline.length === 0) return 0
|
|
210
|
+
|
|
211
|
+
const activityDates = new Set(
|
|
212
|
+
timeline.map(e => e.ts?.split('T')[0]).filter(Boolean)
|
|
336
213
|
)
|
|
214
|
+
|
|
215
|
+
const today = new Date()
|
|
216
|
+
today.setHours(0, 0, 0, 0)
|
|
217
|
+
|
|
218
|
+
const days = Array.from({ length: 30 }, (_, i) => {
|
|
219
|
+
const date = new Date(today)
|
|
220
|
+
date.setDate(date.getDate() - i)
|
|
221
|
+
return date.toISOString().split('T')[0]
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
const firstGapIndex = days.findIndex(date => !activityDates.has(date))
|
|
225
|
+
|
|
226
|
+
if (firstGapIndex === 0) {
|
|
227
|
+
const yesterdayHasActivity = activityDates.has(days[1])
|
|
228
|
+
if (!yesterdayHasActivity) return 0
|
|
229
|
+
const remainingDays = days.slice(1)
|
|
230
|
+
const gapFromYesterday = remainingDays.findIndex(date => !activityDates.has(date))
|
|
231
|
+
return gapFromYesterday === -1 ? remainingDays.length : gapFromYesterday
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return firstGapIndex === -1 ? days.length : firstGapIndex
|
|
337
235
|
}
|
|
338
236
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
|
|
237
|
+
// Get weekly velocity data from legacy timeline
|
|
238
|
+
function getWeeklyDataFromTimeline(stats: StatsResult): number[] {
|
|
239
|
+
const timeline = stats.legacyStats?.timeline ?? []
|
|
240
|
+
if (timeline.length === 0) return []
|
|
343
241
|
|
|
344
|
-
const
|
|
345
|
-
const deleteMutation = useDeleteProject()
|
|
242
|
+
const today = new Date()
|
|
346
243
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
)
|
|
244
|
+
return Array.from({ length: 7 }, (_, i) => {
|
|
245
|
+
const date = new Date(today)
|
|
246
|
+
date.setDate(date.getDate() - (6 - i))
|
|
247
|
+
const dateStr = date.toISOString().split('T')[0]
|
|
248
|
+
|
|
249
|
+
return timeline.filter(e => e.ts?.startsWith(dateStr)).length
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
interface PageProps {
|
|
254
|
+
params: Promise<{ id: string }>
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export default async function ProjectStatsPage({ params }: PageProps) {
|
|
258
|
+
const { id: projectId } = await params
|
|
259
|
+
|
|
260
|
+
// Fetch data directly on server - no API calls
|
|
261
|
+
const [project, stats] = await Promise.all([
|
|
262
|
+
getProject(projectId),
|
|
263
|
+
getStats(projectId)
|
|
264
|
+
])
|
|
265
|
+
|
|
266
|
+
if (!stats.hasData) {
|
|
267
|
+
notFound()
|
|
353
268
|
}
|
|
354
269
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
270
|
+
// Compute derived values using service functions
|
|
271
|
+
// Use legacy timeline for streak/weekly if metrics is empty
|
|
272
|
+
const streak = stats.metrics?.recentActivity?.length
|
|
273
|
+
? calculateStreak(stats.metrics)
|
|
274
|
+
: calculateStreakFromTimeline(stats)
|
|
275
|
+
const velocity = getVelocity(stats)
|
|
276
|
+
const velocityChange = getVelocityChange(velocity)
|
|
277
|
+
const insightMessage = getInsightMessage(stats, streak)
|
|
278
|
+
const weeklyVelocityData = stats.metrics?.recentActivity?.length
|
|
279
|
+
? getWeeklyVelocityData(stats.metrics)
|
|
280
|
+
: getWeeklyDataFromTimeline(stats)
|
|
281
|
+
|
|
282
|
+
// Normalize data for components
|
|
283
|
+
const currentTask = normalizeCurrentTask(stats)
|
|
284
|
+
const queue = normalizeQueue(stats)
|
|
285
|
+
const roadmap = normalizeRoadmap(stats)
|
|
286
|
+
const shipped = normalizeShipped(stats)
|
|
287
|
+
const ideas = normalizeIdeas(stats)
|
|
288
|
+
const agents = normalizeAgents(stats)
|
|
289
|
+
const timeline = normalizeTimeline(stats)
|
|
290
|
+
|
|
291
|
+
// DRY: Use counts from getProject() - same source as dashboard
|
|
292
|
+
const totalShips = project?.shippedCount ?? 0
|
|
293
|
+
const completionRate = project?.completionRate ?? 0
|
|
294
|
+
|
|
295
|
+
// Extract insights
|
|
296
|
+
const { estimateAccuracy, blockers } = stats.insights
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
<div className="flex h-full flex-col p-4 md:p-6 overflow-auto overflow-x-hidden">
|
|
300
|
+
{/* Mobile: Add padding for hamburger menu */}
|
|
301
|
+
<div className="pl-10 md:pl-0">
|
|
302
|
+
<HeroSection
|
|
303
|
+
projectId={projectId}
|
|
304
|
+
projectName={project?.name ?? projectId}
|
|
305
|
+
projectVersion={project?.version}
|
|
306
|
+
totalShips={totalShips}
|
|
307
|
+
completionRate={completionRate}
|
|
308
|
+
streak={streak}
|
|
309
|
+
insightMessage={insightMessage}
|
|
310
|
+
timeline={timeline}
|
|
311
|
+
/>
|
|
312
|
+
</div>
|
|
313
|
+
|
|
314
|
+
{/* Quick Stats Bar */}
|
|
315
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2 sm:gap-3 mt-4 mb-6">
|
|
316
|
+
<div className="bg-card border rounded-lg p-2 sm:p-3 flex items-center gap-2 sm:gap-3">
|
|
317
|
+
<div className="h-8 w-8 sm:h-10 sm:w-10 rounded-lg bg-muted flex items-center justify-center shrink-0">
|
|
318
|
+
<svg className="h-4 w-4 sm:h-5 sm:w-5 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
319
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 3l14 9-14 9V3z" />
|
|
320
|
+
</svg>
|
|
361
321
|
</div>
|
|
362
|
-
<div>
|
|
363
|
-
<
|
|
364
|
-
<
|
|
322
|
+
<div className="min-w-0">
|
|
323
|
+
<div className="text-xl sm:text-2xl font-bold tabular-nums">{totalShips}</div>
|
|
324
|
+
<div className="text-xs text-muted-foreground truncate">Shipped</div>
|
|
365
325
|
</div>
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
</
|
|
326
|
+
</div>
|
|
327
|
+
<div className="bg-card border rounded-lg p-2 sm:p-3 flex items-center gap-2 sm:gap-3">
|
|
328
|
+
<div className="h-8 w-8 sm:h-10 sm:w-10 rounded-lg bg-muted flex items-center justify-center shrink-0">
|
|
329
|
+
<svg className="h-4 w-4 sm:h-5 sm:w-5 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
330
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
|
331
|
+
</svg>
|
|
332
|
+
</div>
|
|
333
|
+
<div className="min-w-0">
|
|
334
|
+
<div className="text-xl sm:text-2xl font-bold tabular-nums">{queue.length}</div>
|
|
335
|
+
<div className="text-xs text-muted-foreground truncate">Queue</div>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
<div className="bg-card border rounded-lg p-2 sm:p-3 flex items-center gap-2 sm:gap-3">
|
|
339
|
+
<div className="h-8 w-8 sm:h-10 sm:w-10 rounded-lg bg-muted flex items-center justify-center shrink-0">
|
|
340
|
+
<svg className="h-4 w-4 sm:h-5 sm:w-5 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
341
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 18.657A8 8 0 016.343 7.343S7 9 9 10c0-2 .5-5 2.986-7C14 5 16.09 5.777 17.656 7.343A7.975 7.975 0 0120 13a7.975 7.975 0 01-2.343 5.657z" />
|
|
342
|
+
</svg>
|
|
343
|
+
</div>
|
|
344
|
+
<div className="min-w-0">
|
|
345
|
+
<div className="text-xl sm:text-2xl font-bold tabular-nums">{streak}</div>
|
|
346
|
+
<div className="text-xs text-muted-foreground truncate">Streak</div>
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
<div className="bg-card border rounded-lg p-2 sm:p-3 flex items-center gap-2 sm:gap-3">
|
|
350
|
+
<div className="h-8 w-8 sm:h-10 sm:w-10 rounded-lg bg-muted flex items-center justify-center shrink-0">
|
|
351
|
+
<svg className="h-4 w-4 sm:h-5 sm:w-5 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
352
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
|
353
|
+
</svg>
|
|
354
|
+
</div>
|
|
355
|
+
<div className="min-w-0">
|
|
356
|
+
<div className="text-xl sm:text-2xl font-bold tabular-nums">{ideas.length}</div>
|
|
357
|
+
<div className="text-xs text-muted-foreground truncate">Ideas</div>
|
|
375
358
|
</div>
|
|
376
|
-
|
|
377
|
-
<AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
|
|
378
|
-
<AlertDialogContent className="max-w-[calc(100vw-2rem)] sm:max-w-lg">
|
|
379
|
-
<AlertDialogHeader>
|
|
380
|
-
<AlertDialogTitle className="flex items-center gap-2">
|
|
381
|
-
<AlertTriangle className="w-5 h-5 text-destructive" />
|
|
382
|
-
Delete Project Data?
|
|
383
|
-
</AlertDialogTitle>
|
|
384
|
-
<AlertDialogDescription>
|
|
385
|
-
This will move the project storage to trash.
|
|
386
|
-
<br />
|
|
387
|
-
<span className="text-muted-foreground text-sm break-all">ID: {projectId}</span>
|
|
388
|
-
</AlertDialogDescription>
|
|
389
|
-
</AlertDialogHeader>
|
|
390
|
-
<AlertDialogFooter className="flex-col sm:flex-row gap-2">
|
|
391
|
-
<AlertDialogCancel disabled={deleteMutation.isPending} className="w-full sm:w-auto">
|
|
392
|
-
Cancel
|
|
393
|
-
</AlertDialogCancel>
|
|
394
|
-
<AlertDialogAction
|
|
395
|
-
onClick={() => deleteMutation.mutate(projectId, { onSuccess: () => router.push('/') })}
|
|
396
|
-
disabled={deleteMutation.isPending}
|
|
397
|
-
className="bg-destructive text-destructive-foreground hover:bg-destructive/90 w-full sm:w-auto"
|
|
398
|
-
>
|
|
399
|
-
{deleteMutation.isPending ? 'Deleting...' : 'Delete'}
|
|
400
|
-
</AlertDialogAction>
|
|
401
|
-
</AlertDialogFooter>
|
|
402
|
-
</AlertDialogContent>
|
|
403
|
-
</AlertDialog>
|
|
404
359
|
</div>
|
|
405
360
|
</div>
|
|
406
|
-
)
|
|
407
|
-
}
|
|
408
361
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
362
|
+
{/* Main Content - Masonry Layout */}
|
|
363
|
+
<StatsMasonry
|
|
364
|
+
projectId={projectId}
|
|
365
|
+
currentTask={currentTask}
|
|
366
|
+
velocity={velocity}
|
|
367
|
+
weeklyVelocityData={weeklyVelocityData}
|
|
368
|
+
velocityChange={velocityChange}
|
|
369
|
+
estimateAccuracy={estimateAccuracy}
|
|
370
|
+
roadmap={roadmap}
|
|
371
|
+
queue={queue}
|
|
372
|
+
shipped={shipped}
|
|
373
|
+
totalShips={totalShips}
|
|
374
|
+
streak={streak}
|
|
375
|
+
blockers={blockers}
|
|
376
|
+
ideas={ideas}
|
|
377
|
+
agents={agents}
|
|
378
|
+
timeline={timeline}
|
|
379
|
+
/>
|
|
380
|
+
|
|
381
|
+
<div className="h-4" />
|
|
382
|
+
</div>
|
|
413
383
|
)
|
|
414
384
|
}
|