From 5803321ae3b3ed08d0658f0cb59f537f3038b96b Mon Sep 17 00:00:00 2001 From: Sushma-stack-hub Date: Wed, 27 May 2026 13:52:17 +0530 Subject: [PATCH 1/2] feat: implement theme-aware coding personality calculator widget --- src/components/CodingPersonaWidget.tsx | 90 ++++++++++++++++++++++++++ src/components/Dashboard.tsx | 13 +++- src/pages/Tracker/Tracker.tsx | 11 +++- src/utils/persona.ts | 68 +++++++++++++++++++ 4 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 src/components/CodingPersonaWidget.tsx create mode 100644 src/utils/persona.ts 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..72601907 --- /dev/null +++ b/src/utils/persona.ts @@ -0,0 +1,68 @@ +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); + + // 🌟 Uses UTC standard hours directly to bypass local machine timezone conversions + const hour = date.getUTCHours(); + + // ☀️ Early Bird Bucket: 5:00 AM to 11:59 AM UTC + if (hour >= 5 && hour < 12) { + earlyBirdCount++; + validTimestampsCount++; + } + // 🦉 Night Owl Bucket: 10:00 PM to 4:59 AM UTC + else if (hour >= 22 || hour < 5) { + nightOwlCount++; + validTimestampsCount++; + } + // 🚀 Mid-Day / Afternoon Builder: 12:00 PM to 9:59 PM UTC + 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 From 5f671ae18ce94fc0a247e6a6ed4f589b361e511d Mon Sep 17 00:00:00 2001 From: Sushma-stack-hub Date: Thu, 28 May 2026 20:22:48 +0530 Subject: [PATCH 2/2] fix(persona): guard against invalid date formats and calculate buckets using local machine hours --- src/utils/persona.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/utils/persona.ts b/src/utils/persona.ts index 72601907..d2b08aeb 100644 --- a/src/utils/persona.ts +++ b/src/utils/persona.ts @@ -23,20 +23,23 @@ export function calculateCodingPersona(items: ContributionItem[]): PersonaResult const date = new Date(dateString); - // 🌟 Uses UTC standard hours directly to bypass local machine timezone conversions - const hour = date.getUTCHours(); + // Fix 1: Guard against invalid timestamps before bucketing to prevent data leakage + if (isNaN(date.getTime())) return; - // ☀️ Early Bird Bucket: 5:00 AM to 11:59 AM UTC + // 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 UTC + // 🦉 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 UTC + // 🚀 Mid-Day / Afternoon Builder: 12:00 PM to 9:59 PM else { midDayCount++; validTimestampsCount++;