diff --git a/src/components/CodingPersonaWidget.tsx b/src/components/CodingPersonaWidget.tsx new file mode 100644 index 00000000..00b03d53 --- /dev/null +++ b/src/components/CodingPersonaWidget.tsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { calculateCodingPersona, ContributionItem } from '../utils/persona'; +import { useTheme } from '@mui/material/styles'; + +interface CodingPersonaWidgetProps { + issues?: ContributionItem[]; + pullRequests?: ContributionItem[]; +} + +export function CodingPersonaWidget({ issues = [], pullRequests = [] }: CodingPersonaWidgetProps) { + // 🌟 Access the active Material UI theme context dynamically + const theme = useTheme(); + const isDarkMode = theme.palette.mode === 'dark'; + + const allContributions = [...issues, ...pullRequests]; + const { personaTitle, earlyBirdPercent, nightOwlPercent, totalCount } = calculateCodingPersona(allContributions); + + if (totalCount === 0) return null; + + return ( +
+
+ + {/* Left Side: Details Text Box */} +
+ + Developer Persona Analyzer + +

+ {personaTitle} +

+

+ Calculated across {totalCount} records +

+
+ + {/* Right Side: Progress Meter Sliders */} +
+
+ + + Morning: {earlyBirdPercent}% + + + + Night: {nightOwlPercent}% + +
+ + {/* Outer Progress Track Backdrop */} +
+
+
+
+
+ +
+
+ ); +} \ No newline at end of file diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx index e28358f0..c6bd4a72 100644 --- a/src/components/Dashboard.tsx +++ b/src/components/Dashboard.tsx @@ -1,3 +1,4 @@ +import { CodingPersonaWidget } from './CodingPersonaWidget'; import React from 'react'; import { PieChart, @@ -74,8 +75,18 @@ const Dashboard: React.FC = ({ totalIssues, totalPrs, data, them ); } + // 🌟 Filter data items dynamically into separate buckets for our calculator widget + const passingIssues = data.filter(item => !item.pull_request); + const passingPrs = data.filter(item => !!item.pull_request); + return ( + + {/* 🌟 BRING IN OUR NEW CALCULATOR ROW (Placed cleanly right above the graphs grid container) */} + + + + {/* Pie Chart: Issues vs PRs */} @@ -139,4 +150,4 @@ const Dashboard: React.FC = ({ totalIssues, totalPrs, data, them ); }; -export default Dashboard; +export default Dashboard; \ No newline at end of file diff --git a/src/pages/Tracker/Tracker.tsx b/src/pages/Tracker/Tracker.tsx index 576f39bf..bb838802 100644 --- a/src/pages/Tracker/Tracker.tsx +++ b/src/pages/Tracker/Tracker.tsx @@ -1,3 +1,4 @@ +import { CodingPersonaWidget } from '../../components/CodingPersonaWidget'; import React, { useState, useEffect } from "react" import { IssueOpenedIcon, @@ -274,6 +275,14 @@ const Home: React.FC = () => { /> + {/* 🌟 FIXED WIDGET ARRAY PROPS DATA PIPELINE 🌟 */} + {/* Feeds both dataset logs together seamlessly to prevent premature pagination caps */} + {!loading && (issues.length > 0 || prs.length > 0) && ( + + + + )} + {/* Tabs + State Filter */} { ); }; -export default Home; +export default Home; \ No newline at end of file diff --git a/src/utils/persona.ts b/src/utils/persona.ts new file mode 100644 index 00000000..d2b08aeb --- /dev/null +++ b/src/utils/persona.ts @@ -0,0 +1,71 @@ +export interface ContributionItem { + created_at?: string; + Created?: string; + [key: string]: any; +} + +export interface PersonaResult { + personaTitle: string; + earlyBirdPercent: number; + nightOwlPercent: number; + totalCount: number; +} + +export function calculateCodingPersona(items: ContributionItem[]): PersonaResult { + let earlyBirdCount = 0; + let nightOwlCount = 0; + let midDayCount = 0; + let validTimestampsCount = 0; + + items.forEach((item) => { + const dateString = item.created_at || item.Created; + if (!dateString) return; + + const date = new Date(dateString); + + // Fix 1: Guard against invalid timestamps before bucketing to prevent data leakage + if (isNaN(date.getTime())) return; + + // Fix 2: Changed to local getHours() to calculate personas based on actual user timezone timing + const hour = date.getHours(); + + // ☀️ Early Bird Bucket: 5:00 AM to 11:59 AM + if (hour >= 5 && hour < 12) { + earlyBirdCount++; + validTimestampsCount++; + } + // 🦉 Night Owl Bucket: 10:00 PM to 4:59 AM + else if (hour >= 22 || hour < 5) { + nightOwlCount++; + validTimestampsCount++; + } + // 🚀 Mid-Day / Afternoon Builder: 12:00 PM to 9:59 PM + else { + midDayCount++; + validTimestampsCount++; + } + }); + + // Calculate percentages based on the complete global dataset logs + const earlyBirdPercent = validTimestampsCount > 0 ? Math.round((earlyBirdCount / validTimestampsCount) * 100) : 0; + const nightOwlPercent = validTimestampsCount > 0 ? Math.round((nightOwlCount / validTimestampsCount) * 100) : 0; + const midDayPercent = validTimestampsCount > 0 ? Math.round((midDayCount / validTimestampsCount) * 100) : 0; + + // Determine the dominant persona title string rule + let personaTitle = "Balanced Builder ⚖️"; + + if (earlyBirdPercent > nightOwlPercent && earlyBirdPercent > midDayPercent) { + personaTitle = "Early Bird ☀️"; + } else if (nightOwlPercent > earlyBirdPercent && nightOwlPercent > midDayPercent) { + personaTitle = "Night Owl 🦉"; + } else if (midDayPercent > earlyBirdPercent && midDayPercent > nightOwlPercent) { + personaTitle = "Productive Peer 🚀"; + } + + return { + personaTitle, + earlyBirdPercent, + nightOwlPercent, + totalCount: validTimestampsCount + }; +} \ No newline at end of file