File size: 4,012 Bytes
98fcc8e
cd201c7
 
98fcc8e
cd201c7
 
 
 
 
 
 
 
 
 
 
 
 
98fcc8e
 
 
 
 
 
 
cd201c7
98fcc8e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cd201c7
98fcc8e
 
 
 
 
 
 
 
 
 
 
 
cd201c7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98fcc8e
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
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;
};

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
  const getColorIntensity = useCallback((level: number) => {
    return getHeatmapColorIntensity(level, color) || undefined;
  }, [color]);
  
  // 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) => (
                  <Tooltip
                    key={`${yearMonth}-${index}`}
                    title={`${activity.count} new repos in week of ${getWeekDateRange(activity.date)}`}
                    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);