Skip to content

Commit bcd609a

Browse files
MarkShawn2020claude
andcommitted
fix: remove unused variables for CI build
- Remove unused React import in main.tsx - Remove unused handlers in WorkspaceView.tsx 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 762488a commit bcd609a

5 files changed

Lines changed: 245 additions & 195 deletions

File tree

src-tauri/src/lib.rs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3171,34 +3171,52 @@ fn get_project_context(project_path: String) -> Result<Vec<ContextFile>, String>
31713171
// Daily Message Stats for Activity Heatmap
31723172
// ============================================================================
31733173

3174+
#[derive(Debug, Serialize, Deserialize)]
3175+
pub struct ActivityStats {
3176+
/// Map of date (YYYY-MM-DD) to count
3177+
pub daily: HashMap<String, usize>,
3178+
/// Map of hour (0-23) to count
3179+
pub hourly: HashMap<u32, usize>,
3180+
/// Map of "date:hour" (YYYY-MM-DD:HH) to count for detailed heatmap
3181+
pub detailed: HashMap<String, usize>,
3182+
}
3183+
31743184
#[tauri::command]
3175-
async fn get_daily_message_stats() -> Result<HashMap<String, usize>, String> {
3185+
async fn get_activity_stats() -> Result<ActivityStats, String> {
31763186
tauri::async_runtime::spawn_blocking(|| {
31773187
let history_path = get_claude_dir().join("history.jsonl");
3178-
let mut daily_counts: HashMap<String, usize> = HashMap::new();
3188+
let mut daily: HashMap<String, usize> = HashMap::new();
3189+
let mut hourly: HashMap<u32, usize> = HashMap::new();
3190+
let mut detailed: HashMap<String, usize> = HashMap::new();
31793191

31803192
if !history_path.exists() {
3181-
return Ok(daily_counts);
3193+
return Ok(ActivityStats { daily, hourly, detailed });
31823194
}
31833195

31843196
if let Ok(content) = fs::read_to_string(&history_path) {
31853197
for line in content.lines() {
31863198
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(line) {
3187-
// timestamp is in milliseconds
31883199
if let Some(ts_ms) = parsed.get("timestamp").and_then(|v| v.as_u64()) {
3189-
// Convert to seconds and create date
31903200
let ts_secs = ts_ms / 1000;
3191-
let datetime = chrono::DateTime::from_timestamp(ts_secs as i64, 0);
3192-
if let Some(dt) = datetime {
3201+
if let Some(dt) = chrono::DateTime::from_timestamp(ts_secs as i64, 0) {
3202+
// Daily count
31933203
let date = dt.format("%Y-%m-%d").to_string();
3194-
*daily_counts.entry(date).or_insert(0) += 1;
3204+
*daily.entry(date.clone()).or_insert(0) += 1;
3205+
3206+
// Hourly count (0-23)
3207+
let hour = dt.format("%H").to_string().parse::<u32>().unwrap_or(0);
3208+
*hourly.entry(hour).or_insert(0) += 1;
3209+
3210+
// Detailed: date + hour
3211+
let date_hour = format!("{}:{:02}", date, hour);
3212+
*detailed.entry(date_hour).or_insert(0) += 1;
31953213
}
31963214
}
31973215
}
31983216
}
31993217
}
32003218

3201-
Ok(daily_counts)
3219+
Ok(ActivityStats { daily, hourly, detailed })
32023220
})
32033221
.await
32043222
.map_err(|e| e.to_string())?
@@ -4448,7 +4466,7 @@ pub fn run() {
44484466
get_project_context,
44494467
get_settings,
44504468
get_command_stats,
4451-
get_daily_message_stats,
4469+
get_activity_stats,
44524470
get_templates_catalog,
44534471
install_command_template,
44544472
rename_command,

src/components/home/ActivityHeatmap.tsx

Lines changed: 202 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,41 @@
1-
import { useMemo } from "react";
1+
import { useMemo, useRef, useEffect, useState } from "react";
2+
3+
type ViewMode = "weekday" | "hour";
24

35
interface ActivityHeatmapProps {
4-
/** Map of date string (YYYY-MM-DD) to session count */
5-
data: Map<string, number>;
6+
/** Map of date (YYYY-MM-DD) to count */
7+
daily: Record<string, number>;
8+
/** Map of "date:hour" (YYYY-MM-DD:HH) to count */
9+
detailed: Record<string, number>;
610
}
711

8-
export function ActivityHeatmap({ data }: ActivityHeatmapProps) {
9-
const { weeks, maxCount, totalSessions } = useMemo(() => {
10-
const today = new Date();
11-
const cells: { date: string; count: number; dayOfWeek: number }[] = [];
12+
export function ActivityHeatmap({ daily, detailed }: ActivityHeatmapProps) {
13+
const [mode, setMode] = useState<ViewMode>("weekday");
14+
const scrollRef = useRef<HTMLDivElement>(null);
1215

13-
// Find earliest date in data to determine range
14-
let earliestDate = today;
15-
data.forEach((_, dateStr) => {
16-
const d = new Date(dateStr);
17-
if (d < earliestDate) earliestDate = d;
18-
});
16+
const dailyMap = useMemo(() => new Map(Object.entries(daily)), [daily]);
17+
const detailedMap = useMemo(() => new Map(Object.entries(detailed)), [detailed]);
1918

20-
// At least show 12 weeks, or extend to cover all data
21-
const minWeeks = 12;
22-
const msPerDay = 24 * 60 * 60 * 1000;
23-
const daysSinceEarliest = Math.ceil((today.getTime() - earliestDate.getTime()) / msPerDay);
24-
const weeksNeeded = Math.max(minWeeks, Math.ceil(daysSinceEarliest / 7) + 1);
25-
const daysToShow = weeksNeeded * 7;
19+
// Weekday mode data (existing logic)
20+
const weekdayData = useMemo(() => {
21+
const today = new Date();
22+
const cells: { date: string; count: number; dayOfWeek: number }[] = [];
23+
const weeksToShow = 52;
24+
const daysToShow = weeksToShow * 7;
2625

27-
// Generate cells for each day
2826
for (let i = daysToShow - 1; i >= 0; i--) {
2927
const d = new Date(today);
3028
d.setDate(d.getDate() - i);
3129
const dateStr = d.toISOString().split("T")[0];
3230
cells.push({
3331
date: dateStr,
34-
count: data.get(dateStr) || 0,
32+
count: dailyMap.get(dateStr) || 0,
3533
dayOfWeek: d.getDay(),
3634
});
3735
}
3836

39-
// Group into weeks (columns)
4037
const weeks: typeof cells[] = [];
4138
let currentWeek: typeof cells = [];
42-
43-
// Pad first week if needed
4439
const firstDayOfWeek = cells[0]?.dayOfWeek || 0;
4540
for (let i = 0; i < firstDayOfWeek; i++) {
4641
currentWeek.push({ date: "", count: 0, dayOfWeek: i });
@@ -53,17 +48,73 @@ export function ActivityHeatmap({ data }: ActivityHeatmapProps) {
5348
}
5449
currentWeek.push(cell);
5550
});
56-
if (currentWeek.length > 0) {
57-
weeks.push(currentWeek);
58-
}
51+
if (currentWeek.length > 0) weeks.push(currentWeek);
52+
53+
const monthLabels: { month: string; weekIdx: number }[] = [];
54+
let lastMonth = -1;
55+
weeks.forEach((week, weekIdx) => {
56+
const firstValidCell = week.find((c) => c.date);
57+
if (firstValidCell) {
58+
const month = new Date(firstValidCell.date).getMonth();
59+
if (month !== lastMonth) {
60+
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
61+
monthLabels.push({ month: monthNames[month], weekIdx });
62+
lastMonth = month;
63+
}
64+
}
65+
});
5966

6067
const maxCount = Math.max(...cells.map((c) => c.count), 1);
6168
const totalSessions = cells.reduce((sum, c) => sum + c.count, 0);
6269

63-
return { weeks, maxCount, totalSessions };
64-
}, [data]);
70+
return { weeks, maxCount, totalSessions, monthLabels };
71+
}, [dailyMap]);
72+
73+
// Hour mode data (横轴日期,纵轴0-23小时)
74+
const hourData = useMemo(() => {
75+
const today = new Date();
76+
const daysToShow = 90; // 3 months for hour view
77+
const days: { date: string; hours: number[] }[] = [];
78+
79+
for (let i = daysToShow - 1; i >= 0; i--) {
80+
const d = new Date(today);
81+
d.setDate(d.getDate() - i);
82+
const dateStr = d.toISOString().split("T")[0];
83+
const hours: number[] = [];
84+
for (let h = 0; h < 24; h++) {
85+
const key = `${dateStr}:${h.toString().padStart(2, "0")}`;
86+
hours.push(detailedMap.get(key) || 0);
87+
}
88+
days.push({ date: dateStr, hours });
89+
}
90+
91+
// Month labels for hour view
92+
const monthLabels: { month: string; dayIdx: number }[] = [];
93+
let lastMonth = -1;
94+
days.forEach((day, dayIdx) => {
95+
const month = new Date(day.date).getMonth();
96+
if (month !== lastMonth) {
97+
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
98+
monthLabels.push({ month: monthNames[month], dayIdx });
99+
lastMonth = month;
100+
}
101+
});
102+
103+
const allCounts = days.flatMap((d) => d.hours);
104+
const maxCount = Math.max(...allCounts, 1);
105+
const totalSessions = allCounts.reduce((sum, c) => sum + c, 0);
65106

66-
const getColorClass = (count: number): string => {
107+
return { days, maxCount, totalSessions, monthLabels };
108+
}, [detailedMap]);
109+
110+
// Scroll to right on mount/mode change
111+
useEffect(() => {
112+
if (scrollRef.current) {
113+
scrollRef.current.scrollLeft = scrollRef.current.scrollWidth;
114+
}
115+
}, [mode, weekdayData, hourData]);
116+
117+
const getColorClass = (count: number, maxCount: number): string => {
67118
if (count === 0) return "bg-muted/30";
68119
const ratio = count / maxCount;
69120
if (ratio < 0.25) return "bg-primary/20";
@@ -72,44 +123,139 @@ export function ActivityHeatmap({ data }: ActivityHeatmapProps) {
72123
return "bg-primary";
73124
};
74125

126+
const cellSize = 11;
127+
const cellGap = 3;
75128
const weekLabels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
129+
const hourLabels = ["0", "3", "6", "9", "12", "15", "18", "21"];
130+
131+
const totalSessions = mode === "weekday" ? weekdayData.totalSessions : hourData.totalSessions;
76132

77133
return (
78-
<div className="space-y-3">
134+
<div className="space-y-2">
79135
<div className="flex items-center justify-between">
80-
<span className="text-xs text-muted-foreground uppercase tracking-wide">
81-
Activity
82-
</span>
136+
<div className="flex items-center gap-2">
137+
<span className="text-xs text-muted-foreground uppercase tracking-wide">
138+
Activity
139+
</span>
140+
{/* Mode toggle */}
141+
<div className="flex rounded-md border border-border/60 overflow-hidden">
142+
<button
143+
onClick={() => setMode("weekday")}
144+
className={`px-2 py-0.5 text-[10px] transition-colors ${
145+
mode === "weekday"
146+
? "bg-primary text-primary-foreground"
147+
: "bg-transparent text-muted-foreground hover:bg-accent"
148+
}`}
149+
>
150+
Week
151+
</button>
152+
<button
153+
onClick={() => setMode("hour")}
154+
className={`px-2 py-0.5 text-[10px] transition-colors ${
155+
mode === "hour"
156+
? "bg-primary text-primary-foreground"
157+
: "bg-transparent text-muted-foreground hover:bg-accent"
158+
}`}
159+
>
160+
Hour
161+
</button>
162+
</div>
163+
</div>
83164
<span className="text-xs text-muted-foreground">
84165
{totalSessions.toLocaleString()} chats
85166
</span>
86167
</div>
87168

88169
<div className="flex gap-1">
89-
{/* Week day labels */}
90-
<div className="flex flex-col gap-[3px] text-[10px] text-muted-foreground/70 pr-1">
91-
{weekLabels.map((label, i) => (
92-
<div key={i} className="h-[11px] flex items-center">
93-
{i % 2 === 1 ? label : ""}
94-
</div>
95-
))}
170+
{/* Y-axis labels */}
171+
<div className="flex flex-col gap-[3px] text-[10px] text-muted-foreground/70 pr-1 pt-4">
172+
{mode === "weekday"
173+
? weekLabels.map((label, i) => (
174+
<div key={i} className="h-[11px] flex items-center">
175+
{i % 2 === 1 ? label : ""}
176+
</div>
177+
))
178+
: Array.from({ length: 24 }, (_, i) => (
179+
<div key={i} className="h-[11px] flex items-center justify-end pr-1">
180+
{hourLabels.includes(i.toString()) ? `${i}h` : ""}
181+
</div>
182+
))}
96183
</div>
97184

98-
{/* Heatmap grid */}
99-
<div className="flex gap-[3px] overflow-x-auto">
100-
{weeks.map((week, weekIdx) => (
101-
<div key={weekIdx} className="flex flex-col gap-[3px]">
102-
{week.map((cell, dayIdx) => (
103-
<div
104-
key={dayIdx}
105-
className={`w-[11px] h-[11px] rounded-sm ${
106-
cell.date ? getColorClass(cell.count) : "bg-transparent"
107-
}`}
108-
title={cell.date ? `${cell.date}: ${cell.count} chats` : ""}
109-
/>
110-
))}
111-
</div>
112-
))}
185+
{/* Scrollable heatmap */}
186+
<div
187+
ref={scrollRef}
188+
className="flex-1 overflow-x-auto scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent"
189+
>
190+
{mode === "weekday" ? (
191+
<>
192+
{/* Month labels */}
193+
<div className="flex h-4 mb-1" style={{ gap: `${cellGap}px` }}>
194+
{weekdayData.weeks.map((_, weekIdx) => {
195+
const label = weekdayData.monthLabels.find((m) => m.weekIdx === weekIdx);
196+
return (
197+
<div
198+
key={weekIdx}
199+
className="text-[10px] text-muted-foreground/70"
200+
style={{ width: `${cellSize}px`, flexShrink: 0 }}
201+
>
202+
{label?.month || ""}
203+
</div>
204+
);
205+
})}
206+
</div>
207+
{/* Grid */}
208+
<div className="flex" style={{ gap: `${cellGap}px` }}>
209+
{weekdayData.weeks.map((week, weekIdx) => (
210+
<div key={weekIdx} className="flex flex-col" style={{ gap: `${cellGap}px` }}>
211+
{week.map((cell, dayIdx) => (
212+
<div
213+
key={dayIdx}
214+
className={`rounded-sm cursor-default ${
215+
cell.date ? getColorClass(cell.count, weekdayData.maxCount) : "bg-transparent"
216+
}`}
217+
style={{ width: `${cellSize}px`, height: `${cellSize}px` }}
218+
title={cell.date ? `${cell.date}: ${cell.count} chats` : ""}
219+
/>
220+
))}
221+
</div>
222+
))}
223+
</div>
224+
</>
225+
) : (
226+
<>
227+
{/* Month labels for hour view */}
228+
<div className="flex h-4 mb-1" style={{ gap: `${cellGap}px` }}>
229+
{hourData.days.map((_, dayIdx) => {
230+
const label = hourData.monthLabels.find((m) => m.dayIdx === dayIdx);
231+
return (
232+
<div
233+
key={dayIdx}
234+
className="text-[10px] text-muted-foreground/70"
235+
style={{ width: `${cellSize}px`, flexShrink: 0 }}
236+
>
237+
{label?.month || ""}
238+
</div>
239+
);
240+
})}
241+
</div>
242+
{/* Grid: each column is a day, each row is an hour */}
243+
<div className="flex" style={{ gap: `${cellGap}px` }}>
244+
{hourData.days.map((day, dayIdx) => (
245+
<div key={dayIdx} className="flex flex-col" style={{ gap: `${cellGap}px` }}>
246+
{day.hours.map((count, hourIdx) => (
247+
<div
248+
key={hourIdx}
249+
className={`rounded-sm cursor-default ${getColorClass(count, hourData.maxCount)}`}
250+
style={{ width: `${cellSize}px`, height: `${cellSize}px` }}
251+
title={`${day.date} ${hourIdx}:00 - ${count} chats`}
252+
/>
253+
))}
254+
</div>
255+
))}
256+
</div>
257+
</>
258+
)}
113259
</div>
114260
</div>
115261

src/main.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import React from "react";
21
import ReactDOM from "react-dom/client";
32
import App from "./App";
43
import "./index.css";

0 commit comments

Comments
 (0)