prjct-cli 0.18.1 → 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 +53 -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/agentic/subagent-generation.md +8 -6
- package/templates/commands/done.md +57 -258
- package/templates/commands/now.md +72 -277
- package/templates/commands/ship.md +55 -261
- package/templates/commands/sync.md +7 -7
- 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,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,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,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
|
-
}
|