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