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.
Files changed (243) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/CLAUDE.md +74 -211
  3. package/core/agentic/prompt-builder.ts +3 -7
  4. package/core/command-registry/optional-commands.ts +0 -20
  5. package/core/infrastructure/command-installer/command-installer.ts +8 -1
  6. package/core/infrastructure/command-installer/global-config.ts +31 -1
  7. package/core/infrastructure/command-installer/index.ts +1 -1
  8. package/core/infrastructure/setup.ts +3 -0
  9. package/package.json +3 -17
  10. package/templates/commands/done.md +57 -258
  11. package/templates/commands/now.md +72 -277
  12. package/templates/commands/ship.md +55 -261
  13. package/templates/commands/test.md +328 -21
  14. package/templates/global/CLAUDE.md +40 -205
  15. package/templates/global/docs/agents.md +88 -0
  16. package/templates/global/docs/architecture.md +103 -0
  17. package/templates/global/docs/commands.md +98 -0
  18. package/templates/global/docs/validation.md +95 -0
  19. package/templates/mcp-config.json +36 -0
  20. package/bin/dev.js +0 -216
  21. package/bin/serve.js +0 -361
  22. package/packages/web/README.md +0 -36
  23. package/packages/web/app/api/claude/sessions/route.ts +0 -44
  24. package/packages/web/app/api/claude/status/route.ts +0 -34
  25. package/packages/web/app/api/projects/[id]/icon/route.ts +0 -33
  26. package/packages/web/app/api/projects/[id]/momentum/route.ts +0 -257
  27. package/packages/web/app/api/projects/[id]/route.ts +0 -29
  28. package/packages/web/app/api/projects/[id]/stats/route.ts +0 -41
  29. package/packages/web/app/api/projects/[id]/status/route.ts +0 -21
  30. package/packages/web/app/api/projects/route.ts +0 -16
  31. package/packages/web/app/api/sessions/current/route.ts +0 -132
  32. package/packages/web/app/api/sessions/history/route.ts +0 -204
  33. package/packages/web/app/error.tsx +0 -34
  34. package/packages/web/app/favicon.ico +0 -0
  35. package/packages/web/app/globals.css +0 -198
  36. package/packages/web/app/layout.tsx +0 -53
  37. package/packages/web/app/loading.tsx +0 -7
  38. package/packages/web/app/not-found.tsx +0 -25
  39. package/packages/web/app/page.tsx +0 -12
  40. package/packages/web/app/project/[id]/code/layout.tsx +0 -18
  41. package/packages/web/app/project/[id]/code/page.tsx +0 -408
  42. package/packages/web/app/project/[id]/error.tsx +0 -41
  43. package/packages/web/app/project/[id]/loading.tsx +0 -9
  44. package/packages/web/app/project/[id]/not-found.tsx +0 -27
  45. package/packages/web/app/project/[id]/page.tsx +0 -384
  46. package/packages/web/app/project/[id]/reports/page.tsx +0 -59
  47. package/packages/web/app/project/[id]/reports/print/page.tsx +0 -58
  48. package/packages/web/app/sessions/page.tsx +0 -165
  49. package/packages/web/app/settings/page.tsx +0 -151
  50. package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +0 -2
  51. package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -49
  52. package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +0 -8
  53. package/packages/web/components/ActivityTimeline/hooks/index.ts +0 -2
  54. package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +0 -9
  55. package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +0 -23
  56. package/packages/web/components/ActivityTimeline/index.ts +0 -2
  57. package/packages/web/components/AgentsCard/AgentsCard.tsx +0 -93
  58. package/packages/web/components/AgentsCard/AgentsCard.types.ts +0 -14
  59. package/packages/web/components/AgentsCard/index.ts +0 -2
  60. package/packages/web/components/AppSidebar/AppSidebar.tsx +0 -316
  61. package/packages/web/components/AppSidebar/index.ts +0 -1
  62. package/packages/web/components/BackLink/BackLink.tsx +0 -18
  63. package/packages/web/components/BackLink/BackLink.types.ts +0 -5
  64. package/packages/web/components/BackLink/index.ts +0 -2
  65. package/packages/web/components/BentoCard/BentoCard.constants.ts +0 -16
  66. package/packages/web/components/BentoCard/BentoCard.tsx +0 -48
  67. package/packages/web/components/BentoCard/BentoCard.types.ts +0 -15
  68. package/packages/web/components/BentoCard/index.ts +0 -2
  69. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +0 -9
  70. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +0 -18
  71. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +0 -5
  72. package/packages/web/components/BentoCardSkeleton/index.ts +0 -2
  73. package/packages/web/components/BentoGrid/BentoGrid.tsx +0 -18
  74. package/packages/web/components/BentoGrid/BentoGrid.types.ts +0 -4
  75. package/packages/web/components/BentoGrid/index.ts +0 -2
  76. package/packages/web/components/BlockersCard/BlockersCard.tsx +0 -75
  77. package/packages/web/components/BlockersCard/BlockersCard.types.ts +0 -12
  78. package/packages/web/components/BlockersCard/index.ts +0 -2
  79. package/packages/web/components/CommandBar/CommandBar.tsx +0 -67
  80. package/packages/web/components/CommandBar/index.ts +0 -1
  81. package/packages/web/components/CommandButton/CommandButton.tsx +0 -46
  82. package/packages/web/components/CommandButton/index.ts +0 -1
  83. package/packages/web/components/ConnectionStatus/ConnectionStatus.tsx +0 -29
  84. package/packages/web/components/ConnectionStatus/index.ts +0 -1
  85. package/packages/web/components/DashboardContent/DashboardContent.tsx +0 -284
  86. package/packages/web/components/DashboardContent/index.ts +0 -1
  87. package/packages/web/components/DateGroup/DateGroup.tsx +0 -18
  88. package/packages/web/components/DateGroup/DateGroup.types.ts +0 -6
  89. package/packages/web/components/DateGroup/DateGroup.utils.ts +0 -11
  90. package/packages/web/components/DateGroup/index.ts +0 -2
  91. package/packages/web/components/EmptyState/EmptyState.tsx +0 -76
  92. package/packages/web/components/EmptyState/EmptyState.types.ts +0 -11
  93. package/packages/web/components/EmptyState/index.ts +0 -2
  94. package/packages/web/components/EventRow/EventRow.constants.ts +0 -10
  95. package/packages/web/components/EventRow/EventRow.tsx +0 -49
  96. package/packages/web/components/EventRow/EventRow.types.ts +0 -7
  97. package/packages/web/components/EventRow/EventRow.utils.ts +0 -49
  98. package/packages/web/components/EventRow/index.ts +0 -2
  99. package/packages/web/components/ExpandButton/ExpandButton.tsx +0 -18
  100. package/packages/web/components/ExpandButton/ExpandButton.types.ts +0 -6
  101. package/packages/web/components/ExpandButton/index.ts +0 -2
  102. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +0 -14
  103. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +0 -5
  104. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +0 -13
  105. package/packages/web/components/HealthGradientBackground/index.ts +0 -2
  106. package/packages/web/components/HeroSection/HeroSection.tsx +0 -92
  107. package/packages/web/components/HeroSection/HeroSection.types.ts +0 -14
  108. package/packages/web/components/HeroSection/HeroSection.utils.ts +0 -11
  109. package/packages/web/components/HeroSection/hooks/index.ts +0 -2
  110. package/packages/web/components/HeroSection/hooks/useCountUp.ts +0 -45
  111. package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +0 -18
  112. package/packages/web/components/HeroSection/index.ts +0 -2
  113. package/packages/web/components/IdeasCard/IdeasCard.tsx +0 -115
  114. package/packages/web/components/IdeasCard/IdeasCard.types.ts +0 -10
  115. package/packages/web/components/IdeasCard/index.ts +0 -2
  116. package/packages/web/components/InsightMessage/InsightMessage.tsx +0 -9
  117. package/packages/web/components/InsightMessage/InsightMessage.types.ts +0 -3
  118. package/packages/web/components/InsightMessage/index.ts +0 -2
  119. package/packages/web/components/Logo/Logo.tsx +0 -65
  120. package/packages/web/components/Logo/index.ts +0 -1
  121. package/packages/web/components/MarkdownContent/MarkdownContent.tsx +0 -123
  122. package/packages/web/components/MarkdownContent/index.ts +0 -1
  123. package/packages/web/components/MasonryGrid/MasonryGrid.tsx +0 -18
  124. package/packages/web/components/MasonryGrid/index.ts +0 -1
  125. package/packages/web/components/MomentumWidget/MomentumWidget.tsx +0 -119
  126. package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +0 -16
  127. package/packages/web/components/MomentumWidget/index.ts +0 -2
  128. package/packages/web/components/NowCard/NowCard.tsx +0 -118
  129. package/packages/web/components/NowCard/NowCard.types.ts +0 -16
  130. package/packages/web/components/NowCard/index.ts +0 -2
  131. package/packages/web/components/PageHeader/PageHeader.tsx +0 -24
  132. package/packages/web/components/PageHeader/index.ts +0 -1
  133. package/packages/web/components/ProgressRing/ProgressRing.constants.ts +0 -20
  134. package/packages/web/components/ProgressRing/ProgressRing.tsx +0 -51
  135. package/packages/web/components/ProgressRing/ProgressRing.types.ts +0 -11
  136. package/packages/web/components/ProgressRing/index.ts +0 -2
  137. package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +0 -54
  138. package/packages/web/components/ProjectAvatar/index.ts +0 -1
  139. package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +0 -37
  140. package/packages/web/components/ProjectColorDot/index.ts +0 -1
  141. package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +0 -104
  142. package/packages/web/components/ProjectSelectorModal/index.ts +0 -1
  143. package/packages/web/components/Providers/Providers.tsx +0 -48
  144. package/packages/web/components/Providers/index.ts +0 -1
  145. package/packages/web/components/QueueCard/QueueCard.tsx +0 -125
  146. package/packages/web/components/QueueCard/QueueCard.types.ts +0 -12
  147. package/packages/web/components/QueueCard/QueueCard.utils.ts +0 -12
  148. package/packages/web/components/QueueCard/index.ts +0 -2
  149. package/packages/web/components/RecoverCard/RecoverCard.tsx +0 -72
  150. package/packages/web/components/RecoverCard/RecoverCard.types.ts +0 -16
  151. package/packages/web/components/RecoverCard/index.ts +0 -2
  152. package/packages/web/components/RoadmapCard/RoadmapCard.tsx +0 -145
  153. package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +0 -16
  154. package/packages/web/components/RoadmapCard/index.ts +0 -2
  155. package/packages/web/components/ShipsCard/ShipsCard.tsx +0 -95
  156. package/packages/web/components/ShipsCard/ShipsCard.types.ts +0 -14
  157. package/packages/web/components/ShipsCard/ShipsCard.utils.ts +0 -4
  158. package/packages/web/components/ShipsCard/index.ts +0 -2
  159. package/packages/web/components/SparklineChart/SparklineChart.tsx +0 -40
  160. package/packages/web/components/SparklineChart/SparklineChart.types.ts +0 -6
  161. package/packages/web/components/SparklineChart/index.ts +0 -2
  162. package/packages/web/components/StatsMasonry/StatsMasonry.tsx +0 -95
  163. package/packages/web/components/StatsMasonry/index.ts +0 -1
  164. package/packages/web/components/StreakCard/StreakCard.constants.ts +0 -2
  165. package/packages/web/components/StreakCard/StreakCard.tsx +0 -55
  166. package/packages/web/components/StreakCard/StreakCard.types.ts +0 -4
  167. package/packages/web/components/StreakCard/index.ts +0 -2
  168. package/packages/web/components/TasksCounter/TasksCounter.tsx +0 -14
  169. package/packages/web/components/TasksCounter/TasksCounter.types.ts +0 -3
  170. package/packages/web/components/TasksCounter/index.ts +0 -2
  171. package/packages/web/components/TechStackBadges/TechStackBadges.tsx +0 -28
  172. package/packages/web/components/TechStackBadges/index.ts +0 -1
  173. package/packages/web/components/TerminalDock/DockToggleTab.tsx +0 -29
  174. package/packages/web/components/TerminalDock/TerminalDock.tsx +0 -386
  175. package/packages/web/components/TerminalDock/TerminalDockTab.tsx +0 -130
  176. package/packages/web/components/TerminalDock/TerminalTabBar.tsx +0 -142
  177. package/packages/web/components/TerminalDock/index.ts +0 -2
  178. package/packages/web/components/TerminalTabs/TerminalTab.tsx +0 -95
  179. package/packages/web/components/TerminalTabs/TerminalTabs.tsx +0 -211
  180. package/packages/web/components/TerminalTabs/index.ts +0 -1
  181. package/packages/web/components/VelocityBadge/VelocityBadge.tsx +0 -32
  182. package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +0 -3
  183. package/packages/web/components/VelocityBadge/index.ts +0 -2
  184. package/packages/web/components/VelocityCard/VelocityCard.tsx +0 -73
  185. package/packages/web/components/VelocityCard/VelocityCard.types.ts +0 -7
  186. package/packages/web/components/VelocityCard/index.ts +0 -2
  187. package/packages/web/components/WeeklyReports/PrintableReport.tsx +0 -259
  188. package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +0 -187
  189. package/packages/web/components/WeeklyReports/WeekCalendar.tsx +0 -288
  190. package/packages/web/components/WeeklyReports/WeeklyReports.tsx +0 -149
  191. package/packages/web/components/WeeklyReports/index.ts +0 -4
  192. package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +0 -25
  193. package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +0 -4
  194. package/packages/web/components/WeeklySparkline/index.ts +0 -2
  195. package/packages/web/components/charts/SessionsChart.tsx +0 -175
  196. package/packages/web/components/ui/alert-dialog.tsx +0 -157
  197. package/packages/web/components/ui/badge.tsx +0 -46
  198. package/packages/web/components/ui/button.tsx +0 -60
  199. package/packages/web/components/ui/card.tsx +0 -92
  200. package/packages/web/components/ui/chart.tsx +0 -385
  201. package/packages/web/components/ui/dialog.tsx +0 -143
  202. package/packages/web/components/ui/drawer.tsx +0 -135
  203. package/packages/web/components/ui/dropdown-menu.tsx +0 -257
  204. package/packages/web/components/ui/input.tsx +0 -21
  205. package/packages/web/components/ui/scroll-area.tsx +0 -58
  206. package/packages/web/components/ui/select.tsx +0 -187
  207. package/packages/web/components/ui/sheet.tsx +0 -139
  208. package/packages/web/components/ui/tabs.tsx +0 -66
  209. package/packages/web/components/ui/tooltip.tsx +0 -61
  210. package/packages/web/components.json +0 -22
  211. package/packages/web/context/GlobalTerminalContext.tsx +0 -538
  212. package/packages/web/context/TerminalContext.tsx +0 -45
  213. package/packages/web/context/TerminalTabsContext.tsx +0 -181
  214. package/packages/web/eslint.config.mjs +0 -18
  215. package/packages/web/hooks/useClaudeTerminal.ts +0 -425
  216. package/packages/web/hooks/useProjectStats.ts +0 -93
  217. package/packages/web/hooks/useProjects.ts +0 -73
  218. package/packages/web/lib/actions/projects.ts +0 -15
  219. package/packages/web/lib/commands.ts +0 -81
  220. package/packages/web/lib/format.ts +0 -23
  221. package/packages/web/lib/generate-week-report.ts +0 -285
  222. package/packages/web/lib/parse-prjct-files.ts +0 -1123
  223. package/packages/web/lib/project-colors.ts +0 -58
  224. package/packages/web/lib/projects.ts +0 -506
  225. package/packages/web/lib/pty.ts +0 -101
  226. package/packages/web/lib/query-config.ts +0 -44
  227. package/packages/web/lib/services/index.ts +0 -9
  228. package/packages/web/lib/services/projects.server.ts +0 -66
  229. package/packages/web/lib/services/stats.server.ts +0 -562
  230. package/packages/web/lib/unified-loader.ts +0 -396
  231. package/packages/web/lib/utils.ts +0 -6
  232. package/packages/web/next-env.d.ts +0 -6
  233. package/packages/web/next.config.ts +0 -7
  234. package/packages/web/package.json +0 -57
  235. package/packages/web/postcss.config.mjs +0 -7
  236. package/packages/web/public/file.svg +0 -1
  237. package/packages/web/public/globe.svg +0 -1
  238. package/packages/web/public/next.svg +0 -1
  239. package/packages/web/public/vercel.svg +0 -1
  240. package/packages/web/public/window.svg +0 -1
  241. package/packages/web/server.ts +0 -312
  242. package/packages/web/tsconfig.json +0 -34
  243. package/templates/commands/serve.md +0 -121
@@ -1,288 +0,0 @@
1
- 'use client'
2
-
3
- import { useRef, useEffect, useState } from 'react'
4
- import { ChevronLeft, ChevronRight } from 'lucide-react'
5
- import { cn } from '@/lib/utils'
6
- import { getCurrentYearWeek, getWeekDateRange, formatDateRange } from '@/lib/generate-week-report'
7
-
8
- export type ActivityLevel = 'none' | 'low' | 'medium' | 'high'
9
-
10
- interface WeekCalendarProps {
11
- year: number
12
- selectedWeeks: number[]
13
- activityLevels: Map<number, ActivityLevel>
14
- onWeekSelect: (weeks: number[]) => void
15
- onYearChange: (year: number) => void
16
- }
17
-
18
- const DAYS = ['M', 'T', 'W', 'T', 'F'] // Mon-Fri
19
-
20
- export function WeekCalendar({
21
- year,
22
- selectedWeeks,
23
- activityLevels,
24
- onWeekSelect,
25
- onYearChange,
26
- }: WeekCalendarProps) {
27
- const { year: currentYear, week: currentWeek } = getCurrentYearWeek()
28
- const isCurrentYear = year === currentYear
29
- const scrollRef = useRef<HTMLDivElement>(null)
30
- const [canScrollLeft, setCanScrollLeft] = useState(false)
31
- const [canScrollRight, setCanScrollRight] = useState(true)
32
-
33
- // Generate 52 weeks
34
- const weeks = Array.from({ length: 52 }, (_, i) => i + 1)
35
-
36
- // Drag to scroll state
37
- const [isDragging, setIsDragging] = useState(false)
38
- const [startX, setStartX] = useState(0)
39
- const [scrollLeftStart, setScrollLeftStart] = useState(0)
40
- const [hasMoved, setHasMoved] = useState(false)
41
-
42
- // Handle mouse down - start potential drag
43
- const handleMouseDown = (e: React.MouseEvent) => {
44
- if (!scrollRef.current) return
45
- setIsDragging(true)
46
- setStartX(e.pageX)
47
- setScrollLeftStart(scrollRef.current.scrollLeft)
48
- setHasMoved(false)
49
- }
50
-
51
- // Handle mouse move - scroll if dragging
52
- const handleMouseMove = (e: React.MouseEvent) => {
53
- if (!isDragging || !scrollRef.current) return
54
-
55
- const diff = e.pageX - startX
56
- // Only count as drag if moved more than 5px
57
- if (Math.abs(diff) > 5) {
58
- setHasMoved(true)
59
- e.preventDefault()
60
- scrollRef.current.scrollLeft = scrollLeftStart - diff
61
- }
62
- }
63
-
64
- // Handle mouse up - end drag
65
- const handleMouseUp = () => {
66
- setIsDragging(false)
67
- }
68
-
69
- // Handle week click (only if not dragging)
70
- const handleWeekClick = (week: number, e: React.MouseEvent) => {
71
- // Ignore click if we were dragging
72
- if (hasMoved) {
73
- e.preventDefault()
74
- return
75
- }
76
-
77
- const isFuture = isCurrentYear && week > currentWeek
78
- if (isFuture) return
79
-
80
- if (selectedWeeks.includes(week)) {
81
- onWeekSelect(selectedWeeks.filter(w => w !== week))
82
- } else {
83
- onWeekSelect([...selectedWeeks, week].sort((a, b) => a - b))
84
- }
85
- }
86
-
87
- // Check scroll state
88
- const updateScrollState = () => {
89
- if (scrollRef.current) {
90
- const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current
91
- setCanScrollLeft(scrollLeft > 0)
92
- setCanScrollRight(scrollLeft < scrollWidth - clientWidth - 10)
93
- }
94
- }
95
-
96
- useEffect(() => {
97
- updateScrollState()
98
- // Scroll to current week on mount
99
- if (scrollRef.current && isCurrentYear) {
100
- const weekElement = scrollRef.current.querySelector(`[data-week="${currentWeek}"]`)
101
- if (weekElement) {
102
- weekElement.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' })
103
- }
104
- }
105
- }, [currentWeek, isCurrentYear])
106
-
107
- const scroll = (direction: 'left' | 'right') => {
108
- if (scrollRef.current) {
109
- const scrollAmount = 400
110
- scrollRef.current.scrollBy({
111
- left: direction === 'left' ? -scrollAmount : scrollAmount,
112
- behavior: 'smooth'
113
- })
114
- }
115
- }
116
-
117
- return (
118
- <div className="space-y-6">
119
- {/* Year header */}
120
- <div className="flex items-center justify-between">
121
- <div className="flex items-center gap-2">
122
- <button
123
- onClick={() => onYearChange(year - 1)}
124
- className="p-1.5 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
125
- >
126
- <ChevronLeft className="h-4 w-4" />
127
- </button>
128
-
129
- <span className="text-4xl font-bold tabular-nums">{year}</span>
130
-
131
- <button
132
- onClick={() => onYearChange(year + 1)}
133
- disabled={year >= currentYear}
134
- className="p-1.5 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted transition-colors disabled:opacity-30 disabled:cursor-not-allowed"
135
- >
136
- <ChevronRight className="h-4 w-4" />
137
- </button>
138
- </div>
139
-
140
- <span className="text-sm text-muted-foreground">
141
- {selectedWeeks.length === 0
142
- ? 'Select weeks'
143
- : selectedWeeks.length === 1
144
- ? `W${selectedWeeks[0]}`
145
- : `${selectedWeeks.length} wks`
146
- }
147
- </span>
148
- </div>
149
-
150
- {/* Horizontal week strip */}
151
- <div className="relative -mx-2">
152
- {/* Scroll buttons */}
153
- {canScrollLeft && (
154
- <button
155
- onClick={() => scroll('left')}
156
- className="absolute left-0 top-1/2 -translate-y-1/2 z-10 p-2 bg-background/80 backdrop-blur rounded-full shadow-lg border hover:bg-muted transition-colors"
157
- >
158
- <ChevronLeft className="h-4 w-4" />
159
- </button>
160
- )}
161
- {canScrollRight && (
162
- <button
163
- onClick={() => scroll('right')}
164
- className="absolute right-0 top-1/2 -translate-y-1/2 z-10 p-2 bg-background/80 backdrop-blur rounded-full shadow-lg border hover:bg-muted transition-colors"
165
- >
166
- <ChevronRight className="h-4 w-4" />
167
- </button>
168
- )}
169
-
170
- {/* Scrollable week cards - drag to scroll */}
171
- <div
172
- ref={scrollRef}
173
- onScroll={updateScrollState}
174
- onMouseDown={handleMouseDown}
175
- onMouseMove={handleMouseMove}
176
- onMouseUp={handleMouseUp}
177
- onMouseLeave={handleMouseUp}
178
- className={cn(
179
- 'flex gap-3 overflow-x-auto scrollbar-hide py-2 px-2',
180
- isDragging && hasMoved ? 'cursor-grabbing' : 'cursor-grab'
181
- )}
182
- style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
183
- >
184
- {weeks.map((week) => {
185
- const isSelected = selectedWeeks.includes(week)
186
- const isCurrent = isCurrentYear && week === currentWeek
187
- const activityLevel = activityLevels.get(week) ?? 'none'
188
- const isFuture = isCurrentYear && week > currentWeek
189
- const { start, end } = getWeekDateRange(year, week)
190
-
191
- // Get month name
192
- const monthName = start.toLocaleDateString('en-US', { month: 'short' })
193
- const dayStart = start.getDate()
194
- const dayEnd = end.getDate()
195
-
196
- return (
197
- <div
198
- key={week}
199
- data-week={week}
200
- onClick={(e) => handleWeekClick(week, e)}
201
- className={cn(
202
- 'flex-shrink-0 w-28 rounded-2xl transition-all duration-200 select-none',
203
- 'flex flex-col overflow-hidden',
204
- 'border',
205
- isFuture && 'opacity-30',
206
- // Selected state
207
- isSelected
208
- ? 'bg-foreground text-background border-foreground shadow-lg scale-105'
209
- : 'bg-card hover:bg-muted border-border',
210
- isCurrent && !isSelected && 'ring-2 ring-primary ring-offset-2 ring-offset-background',
211
- )}
212
- >
213
- {/* Week header */}
214
- <div className={cn(
215
- 'px-3 py-2 text-left border-b',
216
- isSelected ? 'border-background/20' : 'border-border'
217
- )}>
218
- <div className="flex items-baseline gap-0.5">
219
- <span className={cn(
220
- 'text-xs font-medium',
221
- isSelected ? 'text-background/60' : 'text-muted-foreground'
222
- )}>
223
- W
224
- </span>
225
- <span className="text-2xl font-bold tabular-nums">{week}</span>
226
- </div>
227
- <p className={cn(
228
- 'text-xs mt-0.5',
229
- isSelected ? 'text-background/70' : 'text-muted-foreground'
230
- )}>
231
- {monthName} {dayStart}-{dayEnd}
232
- </p>
233
- </div>
234
-
235
- {/* Days grid - Mon to Fri with GitHub-style activity levels */}
236
- <div className="px-3 py-2">
237
- <div className="flex justify-between gap-1">
238
- {DAYS.map((day, i) => {
239
- // Generate activity level per day based on week activity
240
- // This simulates varying activity levels across the week
241
- let dayActivityLevel: 0 | 1 | 2 | 3 | 4 = 0
242
-
243
- if (activityLevel === 'high') {
244
- // High activity: most days are 3-4
245
- dayActivityLevel = [4, 3, 4, 3, 2][i] as 0 | 1 | 2 | 3 | 4
246
- } else if (activityLevel === 'medium') {
247
- // Medium: mix of 2-3
248
- dayActivityLevel = [2, 3, 2, 1, 2][i] as 0 | 1 | 2 | 3 | 4
249
- } else if (activityLevel === 'low') {
250
- // Low: mostly 0-1
251
- dayActivityLevel = [1, 0, 1, 0, 0][i] as 0 | 1 | 2 | 3 | 4
252
- }
253
-
254
- // GitHub-style green gradients
255
- const activityColors = {
256
- 0: isSelected ? 'bg-background/10' : 'bg-muted',
257
- 1: isSelected ? 'bg-background/30' : 'bg-emerald-500/30',
258
- 2: isSelected ? 'bg-background/50' : 'bg-emerald-500/50',
259
- 3: isSelected ? 'bg-background/75' : 'bg-emerald-500/75',
260
- 4: isSelected ? 'bg-background' : 'bg-emerald-500',
261
- }
262
-
263
- return (
264
- <div key={i} className="flex flex-col items-center gap-1">
265
- <span className={cn(
266
- 'text-[10px] font-medium',
267
- isSelected ? 'text-background/50' : 'text-muted-foreground'
268
- )}>
269
- {day}
270
- </span>
271
- <div className={cn(
272
- 'w-3 h-3 rounded-sm',
273
- activityColors[dayActivityLevel]
274
- )} />
275
- </div>
276
- )
277
- })}
278
- </div>
279
- </div>
280
- </div>
281
- )
282
- })}
283
- </div>
284
- </div>
285
-
286
- </div>
287
- )
288
- }
@@ -1,149 +0,0 @@
1
- 'use client'
2
-
3
- import { useState, useMemo } from 'react'
4
- import { useParams } from 'next/navigation'
5
- import Link from 'next/link'
6
- import { RotateCcw, Copy, Check, Printer } from 'lucide-react'
7
- import { WeekCalendar, type ActivityLevel } from './WeekCalendar'
8
- import { ReportPreviewCard } from './ReportPreviewCard'
9
- import {
10
- getCurrentYearWeek,
11
- filterDataByWeek,
12
- generateReportText,
13
- getWeekActivityLevel,
14
- } from '@/lib/generate-week-report'
15
- import type { StatsResult } from '@/lib/services/stats.server'
16
- import { cn } from '@/lib/utils'
17
-
18
- interface WeeklyReportsProps {
19
- stats: StatsResult
20
- projectName: string
21
- }
22
-
23
- export function WeeklyReports({ stats, projectName }: WeeklyReportsProps) {
24
- const params = useParams()
25
- const projectId = params.id as string
26
- const { year: currentYear, week: currentWeek } = getCurrentYearWeek()
27
-
28
- const [year, setYear] = useState(currentYear)
29
- const [selectedWeeks, setSelectedWeeks] = useState<number[]>([currentWeek])
30
- const [copied, setCopied] = useState(false)
31
-
32
- // Build print URL with selected weeks
33
- const printUrl = `/project/${projectId}/reports/print?weeks=${selectedWeeks.join(',')}&year=${year}`
34
-
35
- const activityLevels = useMemo(() => {
36
- const levels = new Map<number, ActivityLevel>()
37
- for (let w = 1; w <= 52; w++) {
38
- levels.set(w, getWeekActivityLevel(stats, year, w))
39
- }
40
- return levels
41
- }, [stats, year])
42
-
43
- const handleReset = () => {
44
- setYear(currentYear)
45
- setSelectedWeeks([currentWeek])
46
- }
47
-
48
- const weekDataList = useMemo(() => {
49
- return selectedWeeks.map(w => filterDataByWeek(stats, year, w))
50
- }, [stats, year, selectedWeeks])
51
-
52
- const reportText = useMemo(() => {
53
- if (weekDataList.length === 0) return ''
54
- return generateReportText(weekDataList, projectName)
55
- }, [weekDataList, projectName])
56
-
57
- const handleCopy = async () => {
58
- try {
59
- await navigator.clipboard.writeText(reportText)
60
- setCopied(true)
61
- setTimeout(() => setCopied(false), 2000)
62
- } catch (err) {
63
- console.error('Failed to copy:', err)
64
- }
65
- }
66
-
67
- return (
68
- <div className="space-y-6">
69
- {/* Calendar */}
70
- <div className="relative overflow-hidden rounded-xl border bg-card p-6">
71
- <WeekCalendar
72
- year={year}
73
- selectedWeeks={selectedWeeks}
74
- activityLevels={activityLevels}
75
- onWeekSelect={setSelectedWeeks}
76
- onYearChange={setYear}
77
- />
78
-
79
- {/* Actions */}
80
- <div className="flex items-center justify-between mt-6 pt-4 border-t">
81
- <button
82
- onClick={handleReset}
83
- className="inline-flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors"
84
- >
85
- <RotateCcw className="h-4 w-4" />
86
- Reset
87
- </button>
88
-
89
- <div className="flex items-center gap-2">
90
- <Link
91
- href={printUrl}
92
- target="_blank"
93
- className={cn(
94
- 'inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg transition-all',
95
- 'border border-foreground/20 text-foreground hover:bg-muted',
96
- selectedWeeks.length === 0 && 'opacity-50 pointer-events-none'
97
- )}
98
- >
99
- <Printer className="h-4 w-4" />
100
- Print / PDF
101
- </Link>
102
-
103
- <button
104
- onClick={handleCopy}
105
- disabled={selectedWeeks.length === 0}
106
- className={cn(
107
- 'inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg transition-all',
108
- copied
109
- ? 'bg-emerald-500 text-white'
110
- : 'bg-foreground text-background hover:bg-foreground/90',
111
- selectedWeeks.length === 0 && 'opacity-50 cursor-not-allowed'
112
- )}
113
- >
114
- {copied ? (
115
- <>
116
- <Check className="h-4 w-4" />
117
- Copied!
118
- </>
119
- ) : (
120
- <>
121
- <Copy className="h-4 w-4" />
122
- Copy
123
- </>
124
- )}
125
- </button>
126
- </div>
127
- </div>
128
- </div>
129
-
130
- {/* Report Preview Card */}
131
- <ReportPreviewCard weekData={weekDataList} />
132
-
133
- {/* Copy Text Preview (collapsed) */}
134
- {selectedWeeks.length > 0 && reportText && (
135
- <details className="group">
136
- <summary className="cursor-pointer text-sm text-muted-foreground hover:text-foreground flex items-center gap-2">
137
- <span className="group-open:rotate-90 transition-transform">▶</span>
138
- View copy text
139
- </summary>
140
- <div className="mt-3 relative overflow-hidden rounded-xl border bg-muted/30 p-4">
141
- <pre className="whitespace-pre-wrap text-sm font-mono text-muted-foreground">
142
- {reportText}
143
- </pre>
144
- </div>
145
- </details>
146
- )}
147
- </div>
148
- )
149
- }
@@ -1,4 +0,0 @@
1
- export { WeeklyReports } from './WeeklyReports'
2
- export { WeekCalendar } from './WeekCalendar'
3
- export { ReportPreviewCard } from './ReportPreviewCard'
4
- export { PrintableReport } from './PrintableReport'
@@ -1,25 +0,0 @@
1
- 'use client'
2
-
3
- import { SparklineChart } from '@/components/SparklineChart'
4
- import type { WeeklySparklineProps } from './WeeklySparkline.types'
5
-
6
- const sizeConfig = {
7
- sm: { width: 'w-full', height: 32 },
8
- md: { width: 'w-full', height: 40 },
9
- lg: { width: 'w-full', height: 56 },
10
- }
11
-
12
- export function WeeklySparkline({ data, size = 'md' }: WeeklySparklineProps) {
13
- const config = sizeConfig[size]
14
-
15
- return (
16
- <div className={`${config.width} min-w-0`}>
17
- <p className="text-xs uppercase tracking-wider text-muted-foreground mb-1 md:text-right">
18
- 7-day activity
19
- </p>
20
- <div className="w-full min-w-0 overflow-hidden">
21
- <SparklineChart data={data} height={config.height} />
22
- </div>
23
- </div>
24
- )
25
- }
@@ -1,4 +0,0 @@
1
- export interface WeeklySparklineProps {
2
- data: number[]
3
- size?: 'sm' | 'md' | 'lg'
4
- }
@@ -1,2 +0,0 @@
1
- export { WeeklySparkline } from './WeeklySparkline'
2
- export type { WeeklySparklineProps } from './WeeklySparkline.types'
@@ -1,175 +0,0 @@
1
- 'use client'
2
-
3
- import * as React from 'react'
4
- import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts'
5
- import { useQuery } from '@tanstack/react-query'
6
- import {
7
- Card,
8
- CardContent,
9
- CardDescription,
10
- CardHeader,
11
- CardTitle,
12
- } from '@/components/ui/card'
13
- import {
14
- ChartConfig,
15
- ChartContainer,
16
- ChartTooltip,
17
- ChartTooltipContent,
18
- } from '@/components/ui/chart'
19
-
20
- const chartConfig = {
21
- views: {
22
- label: 'Activity',
23
- },
24
- tasks: {
25
- label: 'Tasks Completed',
26
- color: 'var(--chart-1)',
27
- },
28
- ships: {
29
- label: 'Features Shipped',
30
- color: 'var(--chart-2)',
31
- },
32
- } satisfies ChartConfig
33
-
34
- interface ChartDataPoint {
35
- date: string
36
- tasks: number
37
- ships: number
38
- }
39
-
40
- interface SessionsHistoryResponse {
41
- success: boolean
42
- data: {
43
- chartData: ChartDataPoint[]
44
- totals: {
45
- tasks: number
46
- ships: number
47
- }
48
- dateRange: {
49
- start: string
50
- end: string
51
- }
52
- }
53
- }
54
-
55
- export function SessionsChart() {
56
- const [activeChart, setActiveChart] =
57
- React.useState<'tasks' | 'ships'>('tasks')
58
-
59
- const { data: response, isLoading } = useQuery<SessionsHistoryResponse>({
60
- queryKey: ['sessions-history'],
61
- queryFn: async () => {
62
- const res = await fetch('/api/sessions/history')
63
- return res.json()
64
- },
65
- })
66
-
67
- const chartData = response?.data?.chartData || []
68
- const totals = response?.data?.totals || { tasks: 0, ships: 0 }
69
-
70
- if (isLoading) {
71
- return (
72
- <Card>
73
- <CardHeader className="flex flex-col items-stretch space-y-0 border-b p-0 sm:flex-row">
74
- <div className="flex flex-1 flex-col justify-center gap-1 px-4 py-3 sm:px-5 sm:py-4">
75
- <CardTitle>Session Activity</CardTitle>
76
- <CardDescription>Loading...</CardDescription>
77
- </div>
78
- </CardHeader>
79
- <CardContent className="px-2 sm:p-4">
80
- <div className="h-[250px] flex items-center justify-center">
81
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
82
- </div>
83
- </CardContent>
84
- </Card>
85
- )
86
- }
87
-
88
- return (
89
- <Card>
90
- <CardHeader className="flex flex-col items-stretch space-y-0 border-b p-0 sm:flex-row">
91
- <div className="flex flex-1 flex-col justify-center gap-1 px-4 py-3 sm:px-5 sm:py-4">
92
- <CardTitle>Session Activity</CardTitle>
93
- <CardDescription>
94
- Last 30 days across all projects
95
- </CardDescription>
96
- </div>
97
- <div className="flex">
98
- {(['tasks', 'ships'] as const).map((key) => {
99
- return (
100
- <button
101
- key={key}
102
- data-active={activeChart === key}
103
- className="relative z-30 flex flex-1 flex-col justify-center gap-1 border-t px-4 py-3 text-left even:border-l data-[active=true]:bg-muted/50 sm:border-l sm:border-t-0 sm:px-6 sm:py-4"
104
- onClick={() => setActiveChart(key)}
105
- >
106
- <span className="text-xs text-muted-foreground">
107
- {chartConfig[key].label}
108
- </span>
109
- <span className="text-lg font-bold leading-none sm:text-3xl">
110
- {totals[key].toLocaleString()}
111
- </span>
112
- </button>
113
- )
114
- })}
115
- </div>
116
- </CardHeader>
117
- <CardContent className="px-2 sm:p-4">
118
- {chartData.length === 0 ? (
119
- <div className="h-[250px] flex items-center justify-center text-muted-foreground">
120
- No session data yet
121
- </div>
122
- ) : (
123
- <ChartContainer
124
- config={chartConfig}
125
- className="aspect-auto h-[250px] w-full"
126
- >
127
- <BarChart
128
- accessibilityLayer
129
- data={chartData}
130
- margin={{
131
- left: 12,
132
- right: 12,
133
- }}
134
- >
135
- <CartesianGrid vertical={false} />
136
- <XAxis
137
- dataKey="date"
138
- tickLine={false}
139
- axisLine={false}
140
- tickMargin={8}
141
- minTickGap={32}
142
- tickFormatter={(value: string) => {
143
- const date = new Date(value)
144
- return date.toLocaleDateString('en-US', {
145
- month: 'short',
146
- day: 'numeric',
147
- })
148
- }}
149
- />
150
- <ChartTooltip
151
- content={({ active, payload, label }) => (
152
- <ChartTooltipContent
153
- active={active}
154
- payload={payload as Parameters<typeof ChartTooltipContent>[0]['payload']}
155
- label={label as string}
156
- className="w-[150px]"
157
- nameKey="views"
158
- labelFormatter={(value) => {
159
- return new Date(value).toLocaleDateString('en-US', {
160
- month: 'short',
161
- day: 'numeric',
162
- year: 'numeric',
163
- })
164
- }}
165
- />
166
- )}
167
- />
168
- <Bar dataKey={activeChart} fill={`var(--color-${activeChart})`} />
169
- </BarChart>
170
- </ChartContainer>
171
- )}
172
- </CardContent>
173
- </Card>
174
- )
175
- }