prjct-cli 0.18.2 → 0.19.0
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 +40 -0
- package/CLAUDE.md +74 -211
- package/core/agentic/prompt-builder.ts +3 -7
- package/core/command-registry/optional-commands.ts +0 -20
- package/core/infrastructure/command-installer/command-installer.ts +8 -1
- package/core/infrastructure/command-installer/global-config.ts +31 -1
- package/core/infrastructure/command-installer/index.ts +1 -1
- package/core/infrastructure/setup.ts +3 -0
- package/package.json +3 -17
- package/templates/commands/done.md +57 -258
- package/templates/commands/now.md +72 -277
- package/templates/commands/ship.md +55 -261
- package/templates/commands/test.md +328 -21
- package/templates/global/CLAUDE.md +40 -205
- package/templates/global/docs/agents.md +88 -0
- package/templates/global/docs/architecture.md +103 -0
- package/templates/global/docs/commands.md +98 -0
- package/templates/global/docs/validation.md +95 -0
- package/templates/mcp-config.json +36 -0
- package/bin/dev.js +0 -216
- package/bin/serve.js +0 -361
- package/packages/web/README.md +0 -36
- package/packages/web/app/api/claude/sessions/route.ts +0 -44
- package/packages/web/app/api/claude/status/route.ts +0 -34
- package/packages/web/app/api/projects/[id]/icon/route.ts +0 -33
- package/packages/web/app/api/projects/[id]/momentum/route.ts +0 -257
- package/packages/web/app/api/projects/[id]/route.ts +0 -29
- package/packages/web/app/api/projects/[id]/stats/route.ts +0 -41
- package/packages/web/app/api/projects/[id]/status/route.ts +0 -21
- package/packages/web/app/api/projects/route.ts +0 -16
- package/packages/web/app/api/sessions/current/route.ts +0 -132
- package/packages/web/app/api/sessions/history/route.ts +0 -204
- package/packages/web/app/error.tsx +0 -34
- package/packages/web/app/favicon.ico +0 -0
- package/packages/web/app/globals.css +0 -198
- package/packages/web/app/layout.tsx +0 -53
- package/packages/web/app/loading.tsx +0 -7
- package/packages/web/app/not-found.tsx +0 -25
- package/packages/web/app/page.tsx +0 -12
- package/packages/web/app/project/[id]/code/layout.tsx +0 -18
- package/packages/web/app/project/[id]/code/page.tsx +0 -408
- package/packages/web/app/project/[id]/error.tsx +0 -41
- package/packages/web/app/project/[id]/loading.tsx +0 -9
- package/packages/web/app/project/[id]/not-found.tsx +0 -27
- package/packages/web/app/project/[id]/page.tsx +0 -384
- package/packages/web/app/project/[id]/reports/page.tsx +0 -59
- package/packages/web/app/project/[id]/reports/print/page.tsx +0 -58
- package/packages/web/app/sessions/page.tsx +0 -165
- package/packages/web/app/settings/page.tsx +0 -151
- package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +0 -2
- package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -49
- package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +0 -8
- package/packages/web/components/ActivityTimeline/hooks/index.ts +0 -2
- package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +0 -9
- package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +0 -23
- package/packages/web/components/ActivityTimeline/index.ts +0 -2
- package/packages/web/components/AgentsCard/AgentsCard.tsx +0 -93
- package/packages/web/components/AgentsCard/AgentsCard.types.ts +0 -14
- package/packages/web/components/AgentsCard/index.ts +0 -2
- package/packages/web/components/AppSidebar/AppSidebar.tsx +0 -316
- package/packages/web/components/AppSidebar/index.ts +0 -1
- package/packages/web/components/BackLink/BackLink.tsx +0 -18
- package/packages/web/components/BackLink/BackLink.types.ts +0 -5
- package/packages/web/components/BackLink/index.ts +0 -2
- package/packages/web/components/BentoCard/BentoCard.constants.ts +0 -16
- package/packages/web/components/BentoCard/BentoCard.tsx +0 -48
- package/packages/web/components/BentoCard/BentoCard.types.ts +0 -15
- package/packages/web/components/BentoCard/index.ts +0 -2
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +0 -9
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +0 -18
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +0 -5
- package/packages/web/components/BentoCardSkeleton/index.ts +0 -2
- package/packages/web/components/BentoGrid/BentoGrid.tsx +0 -18
- package/packages/web/components/BentoGrid/BentoGrid.types.ts +0 -4
- package/packages/web/components/BentoGrid/index.ts +0 -2
- package/packages/web/components/BlockersCard/BlockersCard.tsx +0 -75
- package/packages/web/components/BlockersCard/BlockersCard.types.ts +0 -12
- package/packages/web/components/BlockersCard/index.ts +0 -2
- package/packages/web/components/CommandBar/CommandBar.tsx +0 -67
- package/packages/web/components/CommandBar/index.ts +0 -1
- package/packages/web/components/CommandButton/CommandButton.tsx +0 -46
- package/packages/web/components/CommandButton/index.ts +0 -1
- package/packages/web/components/ConnectionStatus/ConnectionStatus.tsx +0 -29
- package/packages/web/components/ConnectionStatus/index.ts +0 -1
- package/packages/web/components/DashboardContent/DashboardContent.tsx +0 -284
- package/packages/web/components/DashboardContent/index.ts +0 -1
- package/packages/web/components/DateGroup/DateGroup.tsx +0 -18
- package/packages/web/components/DateGroup/DateGroup.types.ts +0 -6
- package/packages/web/components/DateGroup/DateGroup.utils.ts +0 -11
- package/packages/web/components/DateGroup/index.ts +0 -2
- package/packages/web/components/EmptyState/EmptyState.tsx +0 -76
- package/packages/web/components/EmptyState/EmptyState.types.ts +0 -11
- package/packages/web/components/EmptyState/index.ts +0 -2
- package/packages/web/components/EventRow/EventRow.constants.ts +0 -10
- package/packages/web/components/EventRow/EventRow.tsx +0 -49
- package/packages/web/components/EventRow/EventRow.types.ts +0 -7
- package/packages/web/components/EventRow/EventRow.utils.ts +0 -49
- package/packages/web/components/EventRow/index.ts +0 -2
- package/packages/web/components/ExpandButton/ExpandButton.tsx +0 -18
- package/packages/web/components/ExpandButton/ExpandButton.types.ts +0 -6
- package/packages/web/components/ExpandButton/index.ts +0 -2
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +0 -14
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +0 -5
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +0 -13
- package/packages/web/components/HealthGradientBackground/index.ts +0 -2
- package/packages/web/components/HeroSection/HeroSection.tsx +0 -92
- package/packages/web/components/HeroSection/HeroSection.types.ts +0 -14
- package/packages/web/components/HeroSection/HeroSection.utils.ts +0 -11
- package/packages/web/components/HeroSection/hooks/index.ts +0 -2
- package/packages/web/components/HeroSection/hooks/useCountUp.ts +0 -45
- package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +0 -18
- package/packages/web/components/HeroSection/index.ts +0 -2
- package/packages/web/components/IdeasCard/IdeasCard.tsx +0 -115
- package/packages/web/components/IdeasCard/IdeasCard.types.ts +0 -10
- package/packages/web/components/IdeasCard/index.ts +0 -2
- package/packages/web/components/InsightMessage/InsightMessage.tsx +0 -9
- package/packages/web/components/InsightMessage/InsightMessage.types.ts +0 -3
- package/packages/web/components/InsightMessage/index.ts +0 -2
- package/packages/web/components/Logo/Logo.tsx +0 -65
- package/packages/web/components/Logo/index.ts +0 -1
- package/packages/web/components/MarkdownContent/MarkdownContent.tsx +0 -123
- package/packages/web/components/MarkdownContent/index.ts +0 -1
- package/packages/web/components/MasonryGrid/MasonryGrid.tsx +0 -18
- package/packages/web/components/MasonryGrid/index.ts +0 -1
- package/packages/web/components/MomentumWidget/MomentumWidget.tsx +0 -119
- package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +0 -16
- package/packages/web/components/MomentumWidget/index.ts +0 -2
- package/packages/web/components/NowCard/NowCard.tsx +0 -118
- package/packages/web/components/NowCard/NowCard.types.ts +0 -16
- package/packages/web/components/NowCard/index.ts +0 -2
- package/packages/web/components/PageHeader/PageHeader.tsx +0 -24
- package/packages/web/components/PageHeader/index.ts +0 -1
- package/packages/web/components/ProgressRing/ProgressRing.constants.ts +0 -20
- package/packages/web/components/ProgressRing/ProgressRing.tsx +0 -51
- package/packages/web/components/ProgressRing/ProgressRing.types.ts +0 -11
- package/packages/web/components/ProgressRing/index.ts +0 -2
- package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +0 -54
- package/packages/web/components/ProjectAvatar/index.ts +0 -1
- package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +0 -37
- package/packages/web/components/ProjectColorDot/index.ts +0 -1
- package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +0 -104
- package/packages/web/components/ProjectSelectorModal/index.ts +0 -1
- package/packages/web/components/Providers/Providers.tsx +0 -48
- package/packages/web/components/Providers/index.ts +0 -1
- package/packages/web/components/QueueCard/QueueCard.tsx +0 -125
- package/packages/web/components/QueueCard/QueueCard.types.ts +0 -12
- package/packages/web/components/QueueCard/QueueCard.utils.ts +0 -12
- package/packages/web/components/QueueCard/index.ts +0 -2
- package/packages/web/components/RecoverCard/RecoverCard.tsx +0 -72
- package/packages/web/components/RecoverCard/RecoverCard.types.ts +0 -16
- package/packages/web/components/RecoverCard/index.ts +0 -2
- package/packages/web/components/RoadmapCard/RoadmapCard.tsx +0 -145
- package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +0 -16
- package/packages/web/components/RoadmapCard/index.ts +0 -2
- package/packages/web/components/ShipsCard/ShipsCard.tsx +0 -95
- package/packages/web/components/ShipsCard/ShipsCard.types.ts +0 -14
- package/packages/web/components/ShipsCard/ShipsCard.utils.ts +0 -4
- package/packages/web/components/ShipsCard/index.ts +0 -2
- package/packages/web/components/SparklineChart/SparklineChart.tsx +0 -40
- package/packages/web/components/SparklineChart/SparklineChart.types.ts +0 -6
- package/packages/web/components/SparklineChart/index.ts +0 -2
- package/packages/web/components/StatsMasonry/StatsMasonry.tsx +0 -95
- package/packages/web/components/StatsMasonry/index.ts +0 -1
- package/packages/web/components/StreakCard/StreakCard.constants.ts +0 -2
- package/packages/web/components/StreakCard/StreakCard.tsx +0 -55
- package/packages/web/components/StreakCard/StreakCard.types.ts +0 -4
- package/packages/web/components/StreakCard/index.ts +0 -2
- package/packages/web/components/TasksCounter/TasksCounter.tsx +0 -14
- package/packages/web/components/TasksCounter/TasksCounter.types.ts +0 -3
- package/packages/web/components/TasksCounter/index.ts +0 -2
- package/packages/web/components/TechStackBadges/TechStackBadges.tsx +0 -28
- package/packages/web/components/TechStackBadges/index.ts +0 -1
- package/packages/web/components/TerminalDock/DockToggleTab.tsx +0 -29
- package/packages/web/components/TerminalDock/TerminalDock.tsx +0 -386
- package/packages/web/components/TerminalDock/TerminalDockTab.tsx +0 -130
- package/packages/web/components/TerminalDock/TerminalTabBar.tsx +0 -142
- package/packages/web/components/TerminalDock/index.ts +0 -2
- package/packages/web/components/TerminalTabs/TerminalTab.tsx +0 -95
- package/packages/web/components/TerminalTabs/TerminalTabs.tsx +0 -211
- package/packages/web/components/TerminalTabs/index.ts +0 -1
- package/packages/web/components/VelocityBadge/VelocityBadge.tsx +0 -32
- package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +0 -3
- package/packages/web/components/VelocityBadge/index.ts +0 -2
- package/packages/web/components/VelocityCard/VelocityCard.tsx +0 -73
- package/packages/web/components/VelocityCard/VelocityCard.types.ts +0 -7
- package/packages/web/components/VelocityCard/index.ts +0 -2
- package/packages/web/components/WeeklyReports/PrintableReport.tsx +0 -259
- package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +0 -187
- package/packages/web/components/WeeklyReports/WeekCalendar.tsx +0 -288
- package/packages/web/components/WeeklyReports/WeeklyReports.tsx +0 -149
- package/packages/web/components/WeeklyReports/index.ts +0 -4
- package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +0 -25
- package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +0 -4
- package/packages/web/components/WeeklySparkline/index.ts +0 -2
- package/packages/web/components/charts/SessionsChart.tsx +0 -175
- package/packages/web/components/ui/alert-dialog.tsx +0 -157
- package/packages/web/components/ui/badge.tsx +0 -46
- package/packages/web/components/ui/button.tsx +0 -60
- package/packages/web/components/ui/card.tsx +0 -92
- package/packages/web/components/ui/chart.tsx +0 -385
- package/packages/web/components/ui/dialog.tsx +0 -143
- package/packages/web/components/ui/drawer.tsx +0 -135
- package/packages/web/components/ui/dropdown-menu.tsx +0 -257
- package/packages/web/components/ui/input.tsx +0 -21
- package/packages/web/components/ui/scroll-area.tsx +0 -58
- package/packages/web/components/ui/select.tsx +0 -187
- package/packages/web/components/ui/sheet.tsx +0 -139
- package/packages/web/components/ui/tabs.tsx +0 -66
- package/packages/web/components/ui/tooltip.tsx +0 -61
- package/packages/web/components.json +0 -22
- package/packages/web/context/GlobalTerminalContext.tsx +0 -538
- package/packages/web/context/TerminalContext.tsx +0 -45
- package/packages/web/context/TerminalTabsContext.tsx +0 -181
- package/packages/web/eslint.config.mjs +0 -18
- package/packages/web/hooks/useClaudeTerminal.ts +0 -425
- package/packages/web/hooks/useProjectStats.ts +0 -93
- package/packages/web/hooks/useProjects.ts +0 -73
- package/packages/web/lib/actions/projects.ts +0 -15
- package/packages/web/lib/commands.ts +0 -81
- package/packages/web/lib/format.ts +0 -23
- package/packages/web/lib/generate-week-report.ts +0 -285
- package/packages/web/lib/parse-prjct-files.ts +0 -1123
- package/packages/web/lib/project-colors.ts +0 -58
- package/packages/web/lib/projects.ts +0 -506
- package/packages/web/lib/pty.ts +0 -101
- package/packages/web/lib/query-config.ts +0 -44
- package/packages/web/lib/services/index.ts +0 -9
- package/packages/web/lib/services/projects.server.ts +0 -66
- package/packages/web/lib/services/stats.server.ts +0 -562
- package/packages/web/lib/unified-loader.ts +0 -396
- package/packages/web/lib/utils.ts +0 -6
- package/packages/web/next-env.d.ts +0 -6
- package/packages/web/next.config.ts +0 -7
- package/packages/web/package.json +0 -57
- package/packages/web/postcss.config.mjs +0 -7
- package/packages/web/public/file.svg +0 -1
- package/packages/web/public/globe.svg +0 -1
- package/packages/web/public/next.svg +0 -1
- package/packages/web/public/vercel.svg +0 -1
- package/packages/web/public/window.svg +0 -1
- package/packages/web/server.ts +0 -312
- package/packages/web/tsconfig.json +0 -34
- package/templates/commands/serve.md +0 -121
|
@@ -1,384 +0,0 @@
|
|
|
1
|
-
import { notFound } from 'next/navigation'
|
|
2
|
-
import type { Metadata } from 'next'
|
|
3
|
-
import {
|
|
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.`,
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
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
|
-
}
|
|
39
|
-
|
|
40
|
-
interface NormalizedQueueItem {
|
|
41
|
-
task: string
|
|
42
|
-
priority?: 'low' | 'medium' | 'high' | 'critical' | number
|
|
43
|
-
estimatedDuration?: string
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface NormalizedShip {
|
|
47
|
-
name: string
|
|
48
|
-
date: string
|
|
49
|
-
duration?: string
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface NormalizedIdea {
|
|
53
|
-
title: string
|
|
54
|
-
impact?: string
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface NormalizedAgent {
|
|
58
|
-
name: string
|
|
59
|
-
description?: string
|
|
60
|
-
successRate?: number
|
|
61
|
-
tasksCompleted?: number
|
|
62
|
-
bestFor?: string[]
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
interface NormalizedRoadmap {
|
|
66
|
-
phases: Array<{
|
|
67
|
-
name: string
|
|
68
|
-
progress: number
|
|
69
|
-
features?: Array<{ name: string; status: string }>
|
|
70
|
-
}>
|
|
71
|
-
progress: number
|
|
72
|
-
}
|
|
73
|
-
|
|
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)
|
|
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
|
|
235
|
-
}
|
|
236
|
-
|
|
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 []
|
|
241
|
-
|
|
242
|
-
const today = new Date()
|
|
243
|
-
|
|
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()
|
|
268
|
-
}
|
|
269
|
-
|
|
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>
|
|
321
|
-
</div>
|
|
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>
|
|
325
|
-
</div>
|
|
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>
|
|
358
|
-
</div>
|
|
359
|
-
</div>
|
|
360
|
-
</div>
|
|
361
|
-
|
|
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>
|
|
383
|
-
)
|
|
384
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { notFound } from 'next/navigation'
|
|
2
|
-
import Link from 'next/link'
|
|
3
|
-
import type { Metadata } from 'next'
|
|
4
|
-
import { ArrowLeft } from 'lucide-react'
|
|
5
|
-
import { getStats } from '@/lib/services/stats.server'
|
|
6
|
-
import { getProject } from '@/lib/services/projects.server'
|
|
7
|
-
import { getProjectEmoji } from '@/lib/project-colors'
|
|
8
|
-
import { WeeklyReports } from '@/components/WeeklyReports'
|
|
9
|
-
|
|
10
|
-
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
|
|
11
|
-
const { id: projectId } = await params
|
|
12
|
-
const project = await getProject(projectId)
|
|
13
|
-
const projectName = project?.name ?? projectId
|
|
14
|
-
const emoji = getProjectEmoji(projectId)
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
title: `${emoji} ${projectName} / Reports / p.`,
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface PageProps {
|
|
22
|
-
params: Promise<{ id: string }>
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default async function ReportsPage({ params }: PageProps) {
|
|
26
|
-
const { id: projectId } = await params
|
|
27
|
-
|
|
28
|
-
const [project, stats] = await Promise.all([
|
|
29
|
-
getProject(projectId),
|
|
30
|
-
getStats(projectId)
|
|
31
|
-
])
|
|
32
|
-
|
|
33
|
-
if (!stats.hasData) {
|
|
34
|
-
notFound()
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const projectName = project?.name ?? projectId
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<div className="flex h-full flex-col p-4 md:p-6 overflow-auto">
|
|
41
|
-
{/* Header with back navigation */}
|
|
42
|
-
<div className="pl-10 md:pl-0 mb-6">
|
|
43
|
-
<Link
|
|
44
|
-
href={`/project/${projectId}`}
|
|
45
|
-
className="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors mb-4"
|
|
46
|
-
>
|
|
47
|
-
<ArrowLeft className="h-4 w-4" />
|
|
48
|
-
Back to {projectName}
|
|
49
|
-
</Link>
|
|
50
|
-
<h1 className="text-3xl font-bold">Weekly Reports</h1>
|
|
51
|
-
</div>
|
|
52
|
-
|
|
53
|
-
<div className="pl-10 md:pl-0">
|
|
54
|
-
<WeeklyReports stats={stats} projectName={projectName} />
|
|
55
|
-
</div>
|
|
56
|
-
<div className="h-4" />
|
|
57
|
-
</div>
|
|
58
|
-
)
|
|
59
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type { Metadata } from 'next'
|
|
2
|
-
import { getStats } from '@/lib/services/stats.server'
|
|
3
|
-
import { getProject } from '@/lib/projects'
|
|
4
|
-
import { getProjectEmoji } from '@/lib/project-colors'
|
|
5
|
-
import { PrintableReport } from '@/components/WeeklyReports/PrintableReport'
|
|
6
|
-
|
|
7
|
-
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
|
|
8
|
-
const { id: projectId } = await params
|
|
9
|
-
const project = await getProject(projectId)
|
|
10
|
-
const projectName = project?.name ?? projectId
|
|
11
|
-
const emoji = getProjectEmoji(projectId)
|
|
12
|
-
|
|
13
|
-
return {
|
|
14
|
-
title: `${emoji} ${projectName} / Print / p.`,
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface Props {
|
|
19
|
-
params: Promise<{ id: string }>
|
|
20
|
-
searchParams: Promise<{ weeks?: string; year?: string }>
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export default async function PrintReportPage({ params, searchParams }: Props) {
|
|
24
|
-
const { id } = await params
|
|
25
|
-
const { weeks, year } = await searchParams
|
|
26
|
-
|
|
27
|
-
const project = await getProject(id)
|
|
28
|
-
if (!project) {
|
|
29
|
-
return <div className="p-8 text-center">Project not found</div>
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const stats = await getStats(id)
|
|
33
|
-
|
|
34
|
-
// Parse weeks from query params (comma-separated)
|
|
35
|
-
const selectedWeeks = weeks
|
|
36
|
-
? weeks.split(',').map(Number).filter(n => !isNaN(n))
|
|
37
|
-
: [getCurrentWeek()]
|
|
38
|
-
|
|
39
|
-
const selectedYear = year ? parseInt(year) : new Date().getFullYear()
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<PrintableReport
|
|
43
|
-
stats={stats}
|
|
44
|
-
projectName={project.name}
|
|
45
|
-
selectedWeeks={selectedWeeks}
|
|
46
|
-
year={selectedYear}
|
|
47
|
-
/>
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function getCurrentWeek(): number {
|
|
52
|
-
const now = new Date()
|
|
53
|
-
const d = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()))
|
|
54
|
-
const dayNum = d.getUTCDay() || 7
|
|
55
|
-
d.setUTCDate(d.getUTCDate() + 4 - dayNum)
|
|
56
|
-
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1))
|
|
57
|
-
return Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7)
|
|
58
|
-
}
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useQuery } from '@tanstack/react-query'
|
|
4
|
-
import { Clock, Target, CheckCircle, PauseCircle, GitCommit, FileCode } from 'lucide-react'
|
|
5
|
-
|
|
6
|
-
function formatDuration(ms: number): string {
|
|
7
|
-
const hours = Math.floor(ms / 3600000)
|
|
8
|
-
const minutes = Math.floor((ms % 3600000) / 60000)
|
|
9
|
-
if (hours > 0) return `${hours}h ${minutes}m`
|
|
10
|
-
return `${minutes}m`
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function getRelativeTime(dateString: string): string {
|
|
14
|
-
const date = new Date(dateString)
|
|
15
|
-
const now = new Date()
|
|
16
|
-
const diff = now.getTime() - date.getTime()
|
|
17
|
-
const minutes = Math.floor(diff / 60000)
|
|
18
|
-
const hours = Math.floor(diff / 3600000)
|
|
19
|
-
const days = Math.floor(diff / 86400000)
|
|
20
|
-
|
|
21
|
-
if (minutes < 1) return 'just now'
|
|
22
|
-
if (minutes < 60) return `${minutes}m ago`
|
|
23
|
-
if (hours < 24) return `${hours}h ago`
|
|
24
|
-
return `${days}d ago`
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export default function Sessions() {
|
|
28
|
-
const { data: projects } = useQuery({
|
|
29
|
-
queryKey: ['projects'],
|
|
30
|
-
queryFn: async () => {
|
|
31
|
-
const res = await fetch('/api/projects')
|
|
32
|
-
const json = await res.json()
|
|
33
|
-
return json.data || []
|
|
34
|
-
}
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
// Get sessions from first project (for demo)
|
|
38
|
-
const projectId = projects?.[0]?.id
|
|
39
|
-
|
|
40
|
-
const { data: sessions, isLoading } = useQuery({
|
|
41
|
-
queryKey: ['sessions', projectId],
|
|
42
|
-
queryFn: async () => {
|
|
43
|
-
if (!projectId) return []
|
|
44
|
-
const res = await fetch(`/api/sessions?projectId=${projectId}`)
|
|
45
|
-
const json = await res.json()
|
|
46
|
-
return json.data || []
|
|
47
|
-
},
|
|
48
|
-
enabled: !!projectId
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
if (isLoading) {
|
|
52
|
-
return (
|
|
53
|
-
<div className="flex items-center justify-center h-full">
|
|
54
|
-
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
|
|
55
|
-
</div>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<div className="p-6 h-full overflow-auto">
|
|
61
|
-
<header className="mb-6">
|
|
62
|
-
<h1 className="text-2xl font-bold flex items-center gap-2">
|
|
63
|
-
<Clock className="w-6 h-6" />
|
|
64
|
-
Sessions
|
|
65
|
-
</h1>
|
|
66
|
-
<p className="text-muted-foreground mt-1">
|
|
67
|
-
Your work session history
|
|
68
|
-
</p>
|
|
69
|
-
</header>
|
|
70
|
-
|
|
71
|
-
{!sessions?.length ? (
|
|
72
|
-
<div className="border border-dashed border-border rounded-lg p-8 text-center">
|
|
73
|
-
<Clock className="w-12 h-12 text-muted-foreground mx-auto mb-4" />
|
|
74
|
-
<p className="text-muted-foreground">
|
|
75
|
-
No sessions yet. Start one with <code className="bg-muted px-2 py-1 rounded">/p:now</code>
|
|
76
|
-
</p>
|
|
77
|
-
</div>
|
|
78
|
-
) : (
|
|
79
|
-
<div className="space-y-4">
|
|
80
|
-
{sessions.map((session: Session) => (
|
|
81
|
-
<SessionCard key={session.id} session={session} />
|
|
82
|
-
))}
|
|
83
|
-
</div>
|
|
84
|
-
)}
|
|
85
|
-
</div>
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
interface Session {
|
|
90
|
-
id: string
|
|
91
|
-
task: string
|
|
92
|
-
status: 'active' | 'paused' | 'completed'
|
|
93
|
-
startedAt: string
|
|
94
|
-
completedAt?: string
|
|
95
|
-
duration: number
|
|
96
|
-
metrics: {
|
|
97
|
-
filesChanged: number
|
|
98
|
-
linesAdded: number
|
|
99
|
-
linesRemoved: number
|
|
100
|
-
commits: number
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function SessionCard({ session }: { session: Session }) {
|
|
105
|
-
const statusConfig = {
|
|
106
|
-
active: {
|
|
107
|
-
icon: Target,
|
|
108
|
-
color: 'text-green-500',
|
|
109
|
-
bg: 'bg-green-500/10',
|
|
110
|
-
label: 'Active'
|
|
111
|
-
},
|
|
112
|
-
paused: {
|
|
113
|
-
icon: PauseCircle,
|
|
114
|
-
color: 'text-yellow-500',
|
|
115
|
-
bg: 'bg-yellow-500/10',
|
|
116
|
-
label: 'Paused'
|
|
117
|
-
},
|
|
118
|
-
completed: {
|
|
119
|
-
icon: CheckCircle,
|
|
120
|
-
color: 'text-blue-500',
|
|
121
|
-
bg: 'bg-blue-500/10',
|
|
122
|
-
label: 'Completed'
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const status = statusConfig[session.status]
|
|
127
|
-
const StatusIcon = status.icon
|
|
128
|
-
|
|
129
|
-
return (
|
|
130
|
-
<div className="bg-card border border-border rounded-lg p-4">
|
|
131
|
-
<div className="flex items-start justify-between mb-3">
|
|
132
|
-
<div className="flex-1">
|
|
133
|
-
<h3 className="font-medium mb-1">{session.task}</h3>
|
|
134
|
-
<div className="flex items-center gap-3 text-sm text-muted-foreground">
|
|
135
|
-
<span>{getRelativeTime(session.startedAt)}</span>
|
|
136
|
-
<span>•</span>
|
|
137
|
-
<span>{formatDuration(session.duration)}</span>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
|
|
141
|
-
<div className={`flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium ${status.bg} ${status.color}`}>
|
|
142
|
-
<StatusIcon className="w-3 h-3" />
|
|
143
|
-
{status.label}
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
|
|
147
|
-
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
|
148
|
-
<span className="flex items-center gap-1">
|
|
149
|
-
<FileCode className="w-3.5 h-3.5" />
|
|
150
|
-
{session.metrics.filesChanged} files
|
|
151
|
-
</span>
|
|
152
|
-
<span className="flex items-center gap-1 text-green-500">
|
|
153
|
-
+{session.metrics.linesAdded}
|
|
154
|
-
</span>
|
|
155
|
-
<span className="flex items-center gap-1 text-red-500">
|
|
156
|
-
-{session.metrics.linesRemoved}
|
|
157
|
-
</span>
|
|
158
|
-
<span className="flex items-center gap-1">
|
|
159
|
-
<GitCommit className="w-3.5 h-3.5" />
|
|
160
|
-
{session.metrics.commits} commits
|
|
161
|
-
</span>
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
164
|
-
)
|
|
165
|
-
}
|