File size: 4,536 Bytes
98fcc8e
cd201c7
 
98fcc8e
cd201c7
 
 
 
 
66f319d
cd201c7
 
 
 
 
 
 
 
98fcc8e
 
 
 
 
 
 
cd201c7
98fcc8e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cd201c7
98fcc8e
 
 
fbae805
98fcc8e
fbae805
 
98fcc8e
 
 
 
 
cd201c7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66f319d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cd201c7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98fcc8e
cd201c7
 
 
 
 
 
 
 
 
 
98fcc8e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import React, { useMemo, useCallback, useEffect, useState } from "react";
import { Tooltip } from "@mui/material";
import { getWeekDateRange } from "../utils/weeklyCalendar";
import { getHeatmapColorIntensity } from "../utils/heatmapColors";

type WeeklyActivity = {
  date: string;
  count: number;
  level: number;
  items?: string[];
};

type WeeklyHeatmapProps = {
  data: WeeklyActivity[];
  color: string;
};

const WeeklyHeatmap: React.FC<WeeklyHeatmapProps> = ({ data, color }) => {
  // Track theme changes
  const [isDark, setIsDark] = useState(false);
  
  useEffect(() => {
    const checkTheme = () => {
      setIsDark(document.documentElement.classList.contains('dark'));
    };
    
    // Initial check
    checkTheme();
    
    // Watch for theme changes
    const observer = new MutationObserver(checkTheme);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    
    return () => observer.disconnect();
  }, []);
  // Memoize the grouped data to avoid recalculation
  const groupedData = useMemo(() => {
    return data.reduce((acc, activity) => {
      const date = new Date(activity.date);
      const yearMonth = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
      
      if (!acc[yearMonth]) {
        acc[yearMonth] = [];
      }
      acc[yearMonth].push(activity);
      return acc;
    }, {} as Record<string, WeeklyActivity[]>);
  }, [data]);

  // Memoize sorted months
  const sortedMonths = useMemo(() => Object.keys(groupedData).sort(), [groupedData]);

  // Memoize the color intensity function with theme awareness
  const getColorIntensity = useCallback((level: number) => {
    return getHeatmapColorIntensity(level, color, isDark) || undefined;
  }, [color, isDark]);
  
  // Get the exact same empty dot color as ActivityCalendar
  const emptyDotColor = useMemo(() => {
    return isDark ? '#374151' : '#d1d5db';
  }, [isDark]);

  // Get month names
  const getMonthName = (yearMonth: string) => {
    const [year, month] = yearMonth.split('-');
    const date = new Date(parseInt(year), parseInt(month) - 1);
    return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
  };

  return (
    <div className="w-full">
      <div className="flex flex-wrap gap-4 justify-center">
        {sortedMonths.map((yearMonth) => {
          const monthData = groupedData[yearMonth];
          return (
            <div key={yearMonth} className="flex flex-col items-center">
              <div className="text-xs mb-2 text-gray-600 dark:text-gray-400">
                {getMonthName(yearMonth)}
              </div>
              <div className="flex gap-1">
                {monthData.map((activity, index) => {
                  const itemsText = activity.items && activity.items.length > 0 
                    ? activity.items.join(', ') 
                    : 'No releases';
                  
                  const tooltipTitle = activity.count > 0 
                    ? `${activity.count} new repos in week of ${getWeekDateRange(activity.date)}: ${itemsText}`
                    : `No repos in week of ${getWeekDateRange(activity.date)}`;
                    
                  return (
                    <Tooltip
                      key={`${yearMonth}-${index}`}
                      title={tooltipTitle}
                      arrow
                    >
                      <div
                        className="w-3 h-3 rounded-sm cursor-pointer transition-opacity hover:opacity-80"
                        style={{
                          backgroundColor: activity.level === 0 ? emptyDotColor : getColorIntensity(activity.level),
                        }}
                      />
                    </Tooltip>
                  );
                })}
              </div>
            </div>
          );
        })}
      </div>
      
      {/* Legend */}
      <div className="flex items-center justify-center mt-4 text-xs text-gray-600 dark:text-gray-400">
        <span className="mr-2">Less</span>
        <div className="flex gap-1">
          {[0, 1, 2, 3, 4].map((level) => (
            <div
              key={level}
              className="w-2.5 h-2.5 rounded-sm"
              style={{
                backgroundColor: level === 0 ? emptyDotColor : getColorIntensity(level),
              }}
            />
          ))}
        </div>
        <span className="ml-2">More</span>
      </div>
    </div>
  );
};

export default React.memo(WeeklyHeatmap);