diff --git a/package-lock.json b/package-lock.json index abd5913..29676f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "orgexplorer", - "version": "2.0.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "orgexplorer", - "version": "2.0.0", + "version": "1.0.0", "dependencies": { "@tailwindcss/vite": "^4.3.0", "d3": "^7.9.0", diff --git a/src/pages/ContributorsPage.jsx b/src/pages/ContributorsPage.jsx index d0f6370..757571b 100644 --- a/src/pages/ContributorsPage.jsx +++ b/src/pages/ContributorsPage.jsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo } from 'react' +import React, { useState, useMemo, useEffect, useRef } from 'react' import { FiDatabase, FiDownload } from 'react-icons/fi' import { useApp } from '../context/AppContext' import { C, SortTh, PageTitle, LoadMore } from '../components/UI' @@ -6,21 +6,52 @@ import { useSortedData } from '../hooks/useSortedData' import { computeBusFactor, exportContributorsCSV } from '../services/analytics' import { useNavigate } from 'react-router-dom' import EmptyStateCard from '../components/EmptyStateCard' +import { BsFillInfoSquareFill } from "react-icons/bs"; export default function ContributorsPage() { const { model } = useApp() const [search, setSearch] = useState('') - const [shown, setShown] = useState(20) + const [shown, setShown] = useState(20) + const [openInfo, setOpenInfo] = useState(null) + const busFactorRef = useRef(null) + const freshnessRef = useRef(null) + const signalRef = useRef(null) + + useEffect(() => { + const handleClickOutside = (e) => { + if ( + busFactorRef.current && + busFactorRef.current.contains(e.target) + ) return + + if ( + freshnessRef.current && + freshnessRef.current.contains(e.target) + ) return + + if ( + signalRef.current && + signalRef.current.contains(e.target) + ) return + + setOpenInfo(null) + } + + document.addEventListener('mousedown', handleClickOutside) + + return () => + document.removeEventListener('mousedown', handleClickOutside) + }, []) if (!model) return null const { contributors } = model const navigate = useNavigate() - const busFactor = useMemo(() => computeBusFactor(contributors), [contributors]) - const topActive = contributors.slice(0, 10).filter(c => c.freshness > 50).length - const freshPct = contributors.length ? Math.round(topActive / Math.min(10, contributors.length) * 100) : 0 + const busFactor = useMemo(() => computeBusFactor(contributors), [contributors]) + const topActive = contributors.slice(0, 10).filter(c => c.freshness > 50).length + const freshPct = contributors.length ? Math.round(topActive / Math.min(10, contributors.length) * 100) : 0 const connectors = contributors.filter(c => c.isConnector) - const crossOrg = contributors.filter(c => c.isCrossOrg) + const crossOrg = contributors.filter(c => c.isCrossOrg) const filtered = useMemo(() => contributors.filter(c => !search || c.login.toLowerCase().includes(search.toLowerCase())), @@ -30,13 +61,13 @@ export default function ContributorsPage() { const visible = sorted.slice(0, shown) const riskColor = r => r === 'critical' ? 'var(--red)' : r === 'high' ? 'var(--amber)' : 'var(--green)' - const riskBar = r => r === 'critical' ? '90%' : r === 'high' ? '60%' : '25%' + const riskBar = r => r === 'critical' ? '90%' : r === 'high' ? '60%' : '25%' return (
Bus Factor Risk
+ + + + {openInfo === 'busfactor' && ( +Measures contributor concentration risk.
+ ++ Higher values indicate knowledge is distributed across more contributors, + reducing dependency on a small number of individuals. +
+Freshness Index
+ + + + {openInfo === 'freshness' && ( ++ Measures how active and recently engaged the contributor community is. +
+ ++ Higher values indicate stronger project momentum and ongoing maintenance. +
+SIGNALS
+ + + + {openInfo === 'signals' && ( ++ Measures how contributors connect repositories and organizations. +
+ ++ Higher values indicate stronger collaboration and knowledge sharing. +
+High Impact Repositories
+ + ++ Higher scores indicate healthier and more actively maintained repositories. +
++ OrgExplorer evaluates repositories using activity, issue health, + contributor diversity, and lifecycle status. +
+ +|
- {r.name}
- {r.orgLogin && {r.orgLogin} }
- |
- {r.stargazers_count.toLocaleString()} | -{r.forks_count.toLocaleString()} | -30 ? 'var(--red)' : 'var(--text2)' }}>{r.open_issues_count} | -{r.pushed_at?.slice(0, 10)} | -
- {r.description || 'No description provided'} -
-|
+ {r.name}
+ {r.orgLogin && {r.orgLogin} }
+ |
+ {r.stargazers_count.toLocaleString()} | +{r.forks_count.toLocaleString()} | +30 ? 'var(--red)' : 'var(--text2)' }}>{r.open_issues_count} | +{r.pushed_at?.slice(0, 10)} | +
+ {r.description || 'No description provided'} +
+GitHub Authentication
+ + + + {open && ( ++ Your GitHub Personal Access Token (PAT) is stored locally on your + device and is never sent to OrgExplorer servers. +
+ ++ Using a PAT increases GitHub API limits and allows access to + repositories you are authorized to view. +
+