hover text showing repo names
#9
by
evijit
HF Staff
- opened
- src/components/Heatmap.tsx +19 -8
- src/components/WeeklyHeatmap.tsx +25 -14
- src/types/heatmap.ts +1 -0
- src/utils/calendar.ts +25 -6
- src/utils/weeklyCalendar.ts +8 -2
src/components/Heatmap.tsx
CHANGED
|
@@ -106,14 +106,25 @@ const Heatmap: React.FC<HeatmapProps> = ({
|
|
| 106 |
blockSize={11}
|
| 107 |
blockMargin={2}
|
| 108 |
hideTotalCount
|
| 109 |
-
renderBlock={(block, activity) =>
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
/>
|
| 118 |
)}
|
| 119 |
</div>
|
|
|
|
| 106 |
blockSize={11}
|
| 107 |
blockMargin={2}
|
| 108 |
hideTotalCount
|
| 109 |
+
renderBlock={(block, activity) => {
|
| 110 |
+
const activityData = activity as any; // Type assertion since react-activity-calendar may not have our extended type
|
| 111 |
+
const itemsText = activityData.items && activityData.items.length > 0
|
| 112 |
+
? activityData.items.join(', ')
|
| 113 |
+
: 'No releases';
|
| 114 |
+
|
| 115 |
+
const tooltipTitle = activity.count > 0
|
| 116 |
+
? `${activity.count} new repos on ${activity.date}: ${itemsText}`
|
| 117 |
+
: `No repos on ${activity.date}`;
|
| 118 |
+
|
| 119 |
+
return (
|
| 120 |
+
<Tooltip
|
| 121 |
+
title={tooltipTitle}
|
| 122 |
+
arrow
|
| 123 |
+
>
|
| 124 |
+
{block}
|
| 125 |
+
</Tooltip>
|
| 126 |
+
);
|
| 127 |
+
}}
|
| 128 |
/>
|
| 129 |
)}
|
| 130 |
</div>
|
src/components/WeeklyHeatmap.tsx
CHANGED
|
@@ -7,6 +7,7 @@ type WeeklyActivity = {
|
|
| 7 |
date: string;
|
| 8 |
count: number;
|
| 9 |
level: number;
|
|
|
|
| 10 |
};
|
| 11 |
|
| 12 |
type WeeklyHeatmapProps = {
|
|
@@ -80,20 +81,30 @@ const WeeklyHeatmap: React.FC<WeeklyHeatmapProps> = ({ data, color }) => {
|
|
| 80 |
{getMonthName(yearMonth)}
|
| 81 |
</div>
|
| 82 |
<div className="flex gap-1">
|
| 83 |
-
{monthData.map((activity, index) =>
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
>
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
</div>
|
| 98 |
</div>
|
| 99 |
);
|
|
|
|
| 7 |
date: string;
|
| 8 |
count: number;
|
| 9 |
level: number;
|
| 10 |
+
items?: string[];
|
| 11 |
};
|
| 12 |
|
| 13 |
type WeeklyHeatmapProps = {
|
|
|
|
| 81 |
{getMonthName(yearMonth)}
|
| 82 |
</div>
|
| 83 |
<div className="flex gap-1">
|
| 84 |
+
{monthData.map((activity, index) => {
|
| 85 |
+
const itemsText = activity.items && activity.items.length > 0
|
| 86 |
+
? activity.items.join(', ')
|
| 87 |
+
: 'No releases';
|
| 88 |
+
|
| 89 |
+
const tooltipTitle = activity.count > 0
|
| 90 |
+
? `${activity.count} new repos in week of ${getWeekDateRange(activity.date)}: ${itemsText}`
|
| 91 |
+
: `No repos in week of ${getWeekDateRange(activity.date)}`;
|
| 92 |
+
|
| 93 |
+
return (
|
| 94 |
+
<Tooltip
|
| 95 |
+
key={`${yearMonth}-${index}`}
|
| 96 |
+
title={tooltipTitle}
|
| 97 |
+
arrow
|
| 98 |
+
>
|
| 99 |
+
<div
|
| 100 |
+
className="w-3 h-3 rounded-sm cursor-pointer transition-opacity hover:opacity-80"
|
| 101 |
+
style={{
|
| 102 |
+
backgroundColor: activity.level === 0 ? emptyDotColor : getColorIntensity(activity.level),
|
| 103 |
+
}}
|
| 104 |
+
/>
|
| 105 |
+
</Tooltip>
|
| 106 |
+
);
|
| 107 |
+
})}
|
| 108 |
</div>
|
| 109 |
</div>
|
| 110 |
);
|
src/types/heatmap.ts
CHANGED
|
@@ -34,6 +34,7 @@ export interface Activity {
|
|
| 34 |
date: string;
|
| 35 |
count: number;
|
| 36 |
level: number;
|
|
|
|
| 37 |
}
|
| 38 |
|
| 39 |
export interface CalendarData {
|
|
|
|
| 34 |
date: string;
|
| 35 |
count: number;
|
| 36 |
level: number;
|
| 37 |
+
items?: string[]; // Array of item names released on this date
|
| 38 |
}
|
| 39 |
|
| 40 |
export interface CalendarData {
|
src/utils/calendar.ts
CHANGED
|
@@ -25,16 +25,27 @@ export const generateCalendarData = (
|
|
| 25 |
const dayOfWeek = startDate.getDay();
|
| 26 |
startDate.setDate(startDate.getDate() - dayOfWeek);
|
| 27 |
|
| 28 |
-
// create a map to store counts for each provider and date
|
| 29 |
const countMap: Record<string, Record<string, number>> = {};
|
|
|
|
| 30 |
|
| 31 |
modelData.forEach((item) => {
|
| 32 |
const dateString = item.createdAt.split("T")[0];
|
| 33 |
providers.forEach(({ authors }) => {
|
| 34 |
if (authors.some((author) => item.id.startsWith(author))) {
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
}
|
| 39 |
});
|
| 40 |
});
|
|
@@ -44,8 +55,16 @@ export const generateCalendarData = (
|
|
| 44 |
const dateString = d.toISOString().split("T")[0];
|
| 45 |
|
| 46 |
providers.forEach(({ authors }) => {
|
| 47 |
-
const
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
});
|
| 50 |
}
|
| 51 |
|
|
|
|
| 25 |
const dayOfWeek = startDate.getDay();
|
| 26 |
startDate.setDate(startDate.getDate() - dayOfWeek);
|
| 27 |
|
| 28 |
+
// create a map to store counts and item names for each provider and date
|
| 29 |
const countMap: Record<string, Record<string, number>> = {};
|
| 30 |
+
const itemsMap: Record<string, Record<string, string[]>> = {};
|
| 31 |
|
| 32 |
modelData.forEach((item) => {
|
| 33 |
const dateString = item.createdAt.split("T")[0];
|
| 34 |
providers.forEach(({ authors }) => {
|
| 35 |
if (authors.some((author) => item.id.startsWith(author))) {
|
| 36 |
+
const providerKey = authors[0];
|
| 37 |
+
|
| 38 |
+
// Initialize maps if needed
|
| 39 |
+
countMap[providerKey] = countMap[providerKey] || {};
|
| 40 |
+
itemsMap[providerKey] = itemsMap[providerKey] || {};
|
| 41 |
+
|
| 42 |
+
// Increment count
|
| 43 |
+
countMap[providerKey][dateString] =
|
| 44 |
+
(countMap[providerKey][dateString] || 0) + 1;
|
| 45 |
+
|
| 46 |
+
// Add item name to the list
|
| 47 |
+
itemsMap[providerKey][dateString] = itemsMap[providerKey][dateString] || [];
|
| 48 |
+
itemsMap[providerKey][dateString].push(item.id);
|
| 49 |
}
|
| 50 |
});
|
| 51 |
});
|
|
|
|
| 55 |
const dateString = d.toISOString().split("T")[0];
|
| 56 |
|
| 57 |
providers.forEach(({ authors }) => {
|
| 58 |
+
const providerKey = authors[0];
|
| 59 |
+
const count = countMap[providerKey]?.[dateString] || 0;
|
| 60 |
+
const items = itemsMap[providerKey]?.[dateString] || [];
|
| 61 |
+
|
| 62 |
+
data[providerKey].push({
|
| 63 |
+
date: dateString,
|
| 64 |
+
count,
|
| 65 |
+
level: 0,
|
| 66 |
+
items
|
| 67 |
+
});
|
| 68 |
});
|
| 69 |
}
|
| 70 |
|
src/utils/weeklyCalendar.ts
CHANGED
|
@@ -4,7 +4,7 @@ export const aggregateToWeeklyData = (dailyData: Activity[]): Activity[] => {
|
|
| 4 |
if (!dailyData || dailyData.length === 0) return [];
|
| 5 |
|
| 6 |
// Create a map to group activities by week
|
| 7 |
-
const weeklyMap = new Map<string, { count: number; level: number; dates: string[] }>();
|
| 8 |
|
| 9 |
for (const dayActivity of dailyData) {
|
| 10 |
const date = new Date(dayActivity.date);
|
|
@@ -14,13 +14,18 @@ export const aggregateToWeeklyData = (dailyData: Activity[]): Activity[] => {
|
|
| 14 |
const weekKey = weekStart.toISOString().split('T')[0];
|
| 15 |
|
| 16 |
if (!weeklyMap.has(weekKey)) {
|
| 17 |
-
weeklyMap.set(weekKey, { count: 0, level: 0, dates: [] });
|
| 18 |
}
|
| 19 |
|
| 20 |
const weekData = weeklyMap.get(weekKey)!;
|
| 21 |
weekData.count += dayActivity.count;
|
| 22 |
weekData.level = Math.max(weekData.level, dayActivity.level);
|
| 23 |
weekData.dates.push(dayActivity.date);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
}
|
| 25 |
|
| 26 |
// Convert to true weekly data - one entry per week, not per day
|
|
@@ -31,6 +36,7 @@ export const aggregateToWeeklyData = (dailyData: Activity[]): Activity[] => {
|
|
| 31 |
date: weekStartDate, // Use the week start date
|
| 32 |
count: weekInfo.count,
|
| 33 |
level: weekInfo.level,
|
|
|
|
| 34 |
});
|
| 35 |
});
|
| 36 |
|
|
|
|
| 4 |
if (!dailyData || dailyData.length === 0) return [];
|
| 5 |
|
| 6 |
// Create a map to group activities by week
|
| 7 |
+
const weeklyMap = new Map<string, { count: number; level: number; dates: string[]; items: string[] }>();
|
| 8 |
|
| 9 |
for (const dayActivity of dailyData) {
|
| 10 |
const date = new Date(dayActivity.date);
|
|
|
|
| 14 |
const weekKey = weekStart.toISOString().split('T')[0];
|
| 15 |
|
| 16 |
if (!weeklyMap.has(weekKey)) {
|
| 17 |
+
weeklyMap.set(weekKey, { count: 0, level: 0, dates: [], items: [] });
|
| 18 |
}
|
| 19 |
|
| 20 |
const weekData = weeklyMap.get(weekKey)!;
|
| 21 |
weekData.count += dayActivity.count;
|
| 22 |
weekData.level = Math.max(weekData.level, dayActivity.level);
|
| 23 |
weekData.dates.push(dayActivity.date);
|
| 24 |
+
|
| 25 |
+
// Aggregate items from daily data
|
| 26 |
+
if (dayActivity.items) {
|
| 27 |
+
weekData.items.push(...dayActivity.items);
|
| 28 |
+
}
|
| 29 |
}
|
| 30 |
|
| 31 |
// Convert to true weekly data - one entry per week, not per day
|
|
|
|
| 36 |
date: weekStartDate, // Use the week start date
|
| 37 |
count: weekInfo.count,
|
| 38 |
level: weekInfo.level,
|
| 39 |
+
items: weekInfo.items, // Include aggregated items
|
| 40 |
});
|
| 41 |
});
|
| 42 |
|