@@ -258,6 +258,80 @@ export class SummaryViewProvider implements vscode.WebviewViewProvider {
258258 margin: 10px 0 0;
259259 color: var(--vscode-textLink-foreground);
260260 }
261+ .heatmap-container {
262+ margin: 30px 0;
263+ overflow-x: auto;
264+ background: var(--vscode-editor-background);
265+ border: 1px solid var(--vscode-panel-border);
266+ border-radius: 6px;
267+ padding: 20px;
268+ }
269+ .heatmap-wrapper {
270+ display: flex;
271+ flex-direction: column;
272+ width: fit-content;
273+ margin: 0 auto;
274+ }
275+ .months-container {
276+ display: flex;
277+ gap: 30px;
278+ }
279+ .month-grid {
280+ display: flex;
281+ flex-direction: column;
282+ gap: 15px;
283+ }
284+ .month-header {
285+ text-align: center;
286+ font-size: 14px;
287+ color: var(--vscode-foreground);
288+ font-weight: 500;
289+ }
290+ .heatmap-grid {
291+ display: grid;
292+ grid-template-columns: repeat(7, 15px);
293+ grid-auto-rows: 15px;
294+ gap: 4px;
295+ }
296+ .day-labels {
297+ display: grid;
298+ grid-template-columns: repeat(7, 15px);
299+ gap: 4px;
300+ margin-top: 4px;
301+ font-size: 10px;
302+ color: var(--vscode-foreground);
303+ opacity: 0.8;
304+ }
305+ .day-label {
306+ text-align: center;
307+ }
308+ .heatmap-cell {
309+ width: 15px;
310+ height: 15px;
311+ border-radius: 3px;
312+ background-color: var(--vscode-editor-background);
313+ border: 1px solid var(--vscode-panel-border);
314+ transition: all 0.2s ease;
315+ }
316+ .heatmap-cell:hover {
317+ transform: scale(1.1);
318+ }
319+ .heatmap-cell[data-level="0"] { background-color: var(--vscode-editor-background); }
320+ .heatmap-cell[data-level="1"] { background-color: #4a72b0; }
321+ .heatmap-cell[data-level="2"] { background-color: #3861a5; }
322+ .heatmap-cell[data-level="3"] { background-color: #254b91; }
323+ .heatmap-cell[data-level="4"] { background-color: #1a3b7c; }
324+ .heatmap-legend {
325+ display: flex;
326+ align-items: center;
327+ justify-content: center;
328+ gap: 5px;
329+ margin-top: 15px;
330+ padding-top: 15px;
331+ border-top: 1px solid var(--vscode-panel-border);
332+ font-size: 11px;
333+ color: var(--vscode-foreground);
334+ }
261335 </style>
262336 </head>
263337 <body>
@@ -292,6 +366,12 @@ export class SummaryViewProvider implements vscode.WebviewViewProvider {
292366 <p id="all-time-total">Loading...</p>
293367 </div>
294368 </div>
369+ <h2>Coding Activity</h2>
370+ <div class="heatmap-container">
371+ <div class="heatmap-wrapper">
372+ <div class="months-container"></div>
373+ </div>
374+ </div>
295375 <div class="search-form">
296376 <input type="date" id="start-date-search" name="start-date-search">
297377 <input type="date" id="end-date-search" name="end-date-search">
@@ -376,6 +456,9 @@ export class SummaryViewProvider implements vscode.WebviewViewProvider {
376456 .join('')}
377457 </table>
378458 \`;
459+
460+ // Add heatmap creation
461+ createHeatmap(data);
379462 }
380463
381464 function displaySearchResult(entries) {
@@ -406,6 +489,126 @@ export class SummaryViewProvider implements vscode.WebviewViewProvider {
406489 return \`\${hours}h \${mins}m\`;
407490 }
408491
492+ function createMonthGrid(data, date) {
493+ const monthName = new Intl.DateTimeFormat('en-US', { month: 'long' }).format(date);
494+ const firstDay = new Date(date.getFullYear(), date.getMonth(), 1);
495+ const lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0);
496+
497+ const monthGrid = document.createElement('div');
498+ monthGrid.className = 'month-grid';
499+
500+ // Add month header
501+ const header = document.createElement('div');
502+ header.className = 'month-header';
503+ header.textContent = \`\${monthName} \${date.getFullYear()}\`;
504+ monthGrid.appendChild(header);
505+
506+ // Create the grid
507+ const grid = document.createElement('div');
508+ grid.className = 'heatmap-grid';
509+
510+ // Get the day of week (0 = Sunday, ..., 6 = Saturday)
511+ const firstDayOfWeek = firstDay.getDay();
512+
513+ // Create empty cells for padding before the first day
514+ for (let i = 0; i < firstDayOfWeek; i++) {
515+ const cell = document.createElement('div');
516+ cell.className = 'heatmap-cell empty-cell';
517+ cell.style.opacity = '0';
518+ grid.appendChild(cell);
519+ }
520+
521+ // Create cells for each day of the month
522+ let previousCell = null; // Add this line to store previous cell reference
523+ for (let day = 1; day <= lastDay.getDate(); day++) {
524+ const currentDate = new Date(date.getFullYear(), date.getMonth(), day);
525+ const cell = document.createElement('div');
526+ cell.className = 'heatmap-cell';
527+
528+ const dateStr = currentDate.toISOString().split('T')[0];
529+ const minutes = data.dailySummary[dateStr] || 0;
530+ const level = getIntensityLevel(minutes);
531+
532+ // Get the day name (Sunday first)
533+ const dayOfWeek = currentDate.getDay();
534+ const dayNames = [ 'Saturday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
535+
536+ // Set the data-level on the previous cell if it exists
537+ if (previousCell) {
538+ previousCell.setAttribute('data-level', level.toString());
539+ previousCell.title = \`\${dateStr} (\${dayNames[dayOfWeek]}): \${formatTime(minutes)}\`;
540+ }
541+
542+ grid.appendChild(cell);
543+ previousCell = cell; // Store current cell as previous for next iteration
544+ }
545+
546+ // Don't forget to set the attribute for the last cell
547+ if (previousCell) {
548+ const lastDate = new Date(date.getFullYear(), date.getMonth(), lastDay.getDate());
549+ const lastDateStr = lastDate.toISOString().split('T')[0];
550+ const lastMinutes = data.dailySummary[lastDateStr] || 0;
551+ const lastLevel = getIntensityLevel(lastMinutes);
552+ const lastDayName = ['Saturday','Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'][lastDate.getDay()];
553+
554+ previousCell.setAttribute('data-level', lastLevel.toString());
555+ previousCell.title = \`\${lastDateStr} (\${lastDayName}): \${formatTime(lastMinutes)}\`;
556+ }
557+
558+ monthGrid.appendChild(grid);
559+
560+ // Add day labels below the grid with Sunday first
561+ const dayLabels = document.createElement('div');
562+ dayLabels.className = 'day-labels';
563+ const days = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
564+ days.forEach(day => {
565+ const label = document.createElement('div');
566+ label.className = 'day-label';
567+ label.textContent = day;
568+ dayLabels.appendChild(label);
569+ });
570+ monthGrid.appendChild(dayLabels);
571+
572+ return monthGrid;
573+ }
574+
575+ function createHeatmap(data) {
576+ const container = document.querySelector('.heatmap-container');
577+ container.innerHTML = '<div class="heatmap-wrapper"><div class="months-container"></div></div>';
578+
579+ const monthsContainer = container.querySelector('.months-container');
580+ const now = new Date();
581+
582+ // Create grids for the last 3 months in reverse order (most recent first)
583+ for (let i = 2; i >= 0; i--) {
584+ const monthDate = new Date(now.getFullYear(), now.getMonth() - i, 1);
585+ const monthGrid = createMonthGrid(data, monthDate);
586+ monthsContainer.appendChild(monthGrid);
587+ }
588+
589+ // Add legend
590+ const legend = document.createElement('div');
591+ legend.className = 'heatmap-legend';
592+ legend.innerHTML = \`
593+ <span>Less</span>
594+ <div class="heatmap-cell" data-level="0"></div>
595+ <div class="heatmap-cell" data-level="1"></div>
596+ <div class="heatmap-cell" data-level="2"></div>
597+ <div class="heatmap-cell" data-level="3"></div>
598+ <div class="heatmap-cell" data-level="4"></div>
599+ <span>More</span>
600+ \`;
601+ container.querySelector('.heatmap-wrapper').appendChild(legend);
602+ }
603+
604+ function getIntensityLevel(minutes) {
605+ if (minutes === 0) return 0;
606+ if (minutes < 60) return 1; // Less than 1 hour
607+ if (minutes < 180) return 2; // 1-3 hours
608+ if (minutes < 360) return 3; // 3-6 hours
609+ return 4; // More than 6 hours
610+ }
611+
409612 // Request a refresh when the webview becomes visible
410613 vscode.postMessage({ command: 'refresh' });
411614 </script>
0 commit comments