-
-
Notifications
You must be signed in to change notification settings - Fork 43
Feat : add analytics dashboard with charts , insights and network graph #55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| interface Props { | ||
| title: string; | ||
| value: string | number; | ||
| } | ||
|
|
||
| export default function StatCard({ title, value }: Props) { | ||
| return ( | ||
| <div className="bg-[#1F2937] p-4 rounded-xl h-32"> | ||
| <p className="text-gray-400 text-sm">{title}</p> | ||
| <h2 className="text-2xl font-bold text-green-400">{value}</h2> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,121 @@ | ||||||||||||||
| import { useState, useMemo } from "react"; | ||||||||||||||
| import { | ||||||||||||||
| LineChart, Line, | ||||||||||||||
| BarChart, Bar, | ||||||||||||||
| PieChart, Pie, Cell, LabelList, | ||||||||||||||
| XAxis, YAxis, Tooltip, Legend | ||||||||||||||
| } from "recharts"; | ||||||||||||||
|
|
||||||||||||||
| const tooltipFormatter = ( | ||||||||||||||
| value?: number | string, | ||||||||||||||
| name?: string | ||||||||||||||
| ): [string, string] => { | ||||||||||||||
| return [ | ||||||||||||||
| value !== undefined ? value.toString() : "-", | ||||||||||||||
| name !== undefined ? name : "-" | ||||||||||||||
| ]; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| export default function ActivityChart({ repos }: any) { | ||||||||||||||
| const [chartType, setChartType] = useState("line"); | ||||||||||||||
| const [filter, setFilter] = useState("top"); | ||||||||||||||
|
|
||||||||||||||
| const COLORS = ["#22c55e", "#3b82f6", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"]; | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Move ♻️ Proposed fix+const COLORS = ["#22c55e", "#3b82f6", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"];
+
export default function ActivityChart({ repos }: any) {
const [chartType, setChartType] = useState("line");
const [filter, setFilter] = useState("top");
-
- const COLORS = ["#22c55e", "#3b82f6", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"];📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| const data = useMemo(() => { | ||||||||||||||
| let processed = [...repos]; | ||||||||||||||
|
|
||||||||||||||
| if (filter === "top") { | ||||||||||||||
| processed.sort((a, b) => b.stargazers_count - a.stargazers_count); | ||||||||||||||
| } | ||||||||||||||
| if (filter === "inactive") { | ||||||||||||||
| const now = new Date().getTime(); | ||||||||||||||
| processed = processed.filter(r => | ||||||||||||||
| now - new Date(r.updated_at).getTime() > 90 * 24 * 60 * 60 * 1000 | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return processed.slice(0, 8).map((r: any) => ({ | ||||||||||||||
| name: r.name, | ||||||||||||||
| stars: r.stargazers_count, | ||||||||||||||
| forks: r.forks_count | ||||||||||||||
| })); | ||||||||||||||
|
Comment on lines
+38
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Replace 🔧 Proposed fix- return processed.slice(0, 8).map((r: any) => ({
+ return processed.slice(0, 8).map((r) => ({
name: r.name,
stars: r.stargazers_count,
forks: r.forks_count
}));With proper typing on 🤖 Prompt for AI Agents |
||||||||||||||
| }, [repos, filter]); | ||||||||||||||
|
|
||||||||||||||
| return ( | ||||||||||||||
| <div className="bg-[#1F2937] p-4 rounded mt-6 text-white" > | ||||||||||||||
|
|
||||||||||||||
| <div className="flex gap-3 mb-4"> | ||||||||||||||
| {["line", "bar", "pie"].map(type => ( | ||||||||||||||
| <button | ||||||||||||||
| key={type} | ||||||||||||||
| onClick={() => setChartType(type)} | ||||||||||||||
| className={`px-3 py-1 rounded ${chartType === type ? "bg-green-500" : "bg-gray-700"}`} | ||||||||||||||
| > | ||||||||||||||
| {type.charAt(0).toUpperCase() + type.slice(1)} | ||||||||||||||
| </button> | ||||||||||||||
| ))} | ||||||||||||||
| </div> | ||||||||||||||
|
|
||||||||||||||
| <div className="flex gap-3 mb-4"> | ||||||||||||||
| <button | ||||||||||||||
| onClick={() => setFilter("top")} | ||||||||||||||
| className={`px-3 py-1 rounded ${filter === "top" ? "bg-blue-500" : "bg-gray-700"}`} | ||||||||||||||
| > | ||||||||||||||
| Top Repos | ||||||||||||||
| </button> | ||||||||||||||
| <button | ||||||||||||||
| onClick={() => setFilter("inactive")} | ||||||||||||||
| className={`px-3 py-1 rounded ${filter === "inactive" ? "bg-blue-500" : "bg-gray-700"}`} | ||||||||||||||
| > | ||||||||||||||
| Inactive | ||||||||||||||
| </button> | ||||||||||||||
| </div> | ||||||||||||||
|
|
||||||||||||||
| {chartType === "line" && ( | ||||||||||||||
| <LineChart width={600} height={300} data={data}> | ||||||||||||||
| <XAxis dataKey="name" /> | ||||||||||||||
| <YAxis /> | ||||||||||||||
| <Tooltip /> | ||||||||||||||
| <Legend /> | ||||||||||||||
| <Line type="monotone" dataKey="stars" stroke="#22c55e" /> | ||||||||||||||
| <Line type="monotone" dataKey="forks" stroke="#3b82f6" /> | ||||||||||||||
| </LineChart> | ||||||||||||||
| )} | ||||||||||||||
|
|
||||||||||||||
| {chartType === "bar" && ( | ||||||||||||||
| <BarChart width={600} height={300} data={data}> | ||||||||||||||
| <XAxis dataKey="name" /> | ||||||||||||||
| <YAxis /> | ||||||||||||||
| <Tooltip /> | ||||||||||||||
| <Legend /> | ||||||||||||||
| <Bar dataKey="stars" fill="#22c55e"> | ||||||||||||||
| <LabelList dataKey="stars" position="top" /> | ||||||||||||||
| </Bar> | ||||||||||||||
| <Bar dataKey="forks" fill="#3b82f6"> | ||||||||||||||
| <LabelList dataKey="forks" position="top" /> | ||||||||||||||
| </Bar> | ||||||||||||||
| </BarChart> | ||||||||||||||
| )} | ||||||||||||||
|
|
||||||||||||||
| {chartType === "pie" && ( | ||||||||||||||
| <PieChart width={400} height={400}> | ||||||||||||||
| <Pie | ||||||||||||||
| data={data} | ||||||||||||||
| dataKey="stars" | ||||||||||||||
| nameKey="name" | ||||||||||||||
| outerRadius={100} | ||||||||||||||
| label={({ name, value }) => `${name}: ${value}`} | ||||||||||||||
| > | ||||||||||||||
| {data.map((_, index) => ( | ||||||||||||||
| <Cell key={index} fill={COLORS[index % COLORS.length]} /> | ||||||||||||||
| ))} | ||||||||||||||
| </Pie> | ||||||||||||||
| <Tooltip formatter={tooltipFormatter} /> | ||||||||||||||
| <Legend /> | ||||||||||||||
| </PieChart> | ||||||||||||||
| )} | ||||||||||||||
|
|
||||||||||||||
| </div> | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,212 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ForceGraph2D from "react-force-graph-2d"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useEffect, useState, useRef } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { fetchRepoContributors } from "../../services/githubService"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, examine the NetworkGraph component
cat -n src/components/Graph/NetworkGraph.tsx | head -50Repository: AOSSIE-Org/OrgExplorer Length of output: 1751 🏁 Script executed: # Then examine the githubService to see the Authorization header implementation
cat -n src/services/githubService.tsRepository: AOSSIE-Org/OrgExplorer Length of output: 832 🏁 Script executed: # Also check if there's any token in environment variables or config
find . -type f \( -name ".env*" -o -name "*.config.*" -o -name "config.*" \) | grep -v node_modules | head -20Repository: AOSSIE-Org/OrgExplorer Length of output: 160 🏁 Script executed: # Check if NetworkGraph has "use client" directive
grep -n "use client" src/components/Graph/NetworkGraph.tsxRepository: AOSSIE-Org/OrgExplorer Length of output: 48 🏁 Script executed: # Get full NetworkGraph file
wc -l src/components/Graph/NetworkGraph.tsxRepository: AOSSIE-Org/OrgExplorer Length of output: 107 🏁 Script executed: # Search for API routes or backend proxy for GitHub
find . -path ./node_modules -prune -o -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" \) -print | xargs grep -l "fetchRepoContributors\|github" | grep -E "api|routes|proxy" | head -20Repository: AOSSIE-Org/OrgExplorer Length of output: 48 🏁 Script executed: # Check vite.config.ts for env variable handling
cat -n vite.config.tsRepository: AOSSIE-Org/OrgExplorer Length of output: 353 🏁 Script executed: # Read full NetworkGraph file to understand complete usage pattern
cat -n src/components/Graph/NetworkGraph.tsxRepository: AOSSIE-Org/OrgExplorer Length of output: 5605 🏁 Script executed: # Check project structure - look for API routes or backend endpoints
ls -la src/ | head -30Repository: AOSSIE-Org/OrgExplorer Length of output: 735 🏁 Script executed: # Check if there are any API routes in a common structure
find . -path ./node_modules -prune -o -type d -name "api" -o -type d -name "routes" -print | head -20Repository: AOSSIE-Org/OrgExplorer Length of output: 48 Move GitHub API calls to a backend API route to prevent token exposure in client-side code.
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import * as d3 from "d3-force"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default function NetworkGraph({ repos }: any) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [graphData, setGraphData] = useState<any>({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nodes: [], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| links: [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fgRef = useRef<any>(null); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pervasive use of Per coding guidelines, avoid 🔧 Suggested type definitionsimport type { Repo } from "../../types/github";
interface GraphNode {
id: string;
type: "repo" | "user";
stars?: number;
contributions?: number;
avatar?: string;
x?: number;
y?: number;
fx?: number;
fy?: number;
}
interface GraphLink {
source: string;
target: string;
weight: number;
}
interface GraphData {
nodes: GraphNode[];
links: GraphLink[];
}
export default function NetworkGraph({ repos }: { repos: Repo[] }) {
const [graphData, setGraphData] = useState<GraphData>({
nodes: [],
links: []
});
// ...
}As per coding guidelines: "Avoid 'any', use explicit types". 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // IMAGE CACHE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const imageCache = useRef<{ [key: string]: HTMLImageElement }>({}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // BUILD GRAPH | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const buildGraph = async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const nodes: any[] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const links: any[] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const addedUsers = new Set(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (let repo of repos.slice(0, 6)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nodes.push({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: repo.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "repo", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stars: repo.stargazers_count | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const contributors = await fetchRepoContributors(repo.contributors_url); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify the Repo type definition and check if contributors_url is used elsewhere
echo "=== Repo type definition ==="
cat src/types/github.ts
echo -e "\n=== Usage of contributors_url ==="
rg -n "contributors_url" --type tsRepository: AOSSIE-Org/OrgExplorer Length of output: 547 🏁 Script executed: #!/bin/bash
# Check where repos are created and how they're hydrated
echo "=== Search for where repos are fetched/created ==="
rg -n "fetchRepo|new Repo|repos =" --type ts -A 2 | head -50
echo -e "\n=== Check tsconfig for strict mode ==="
cat tsconfig.json | grep -A 5 -B 5 "strict"
echo -e "\n=== Check fetchRepoContributors implementation ==="
rg -n "fetchRepoContributors" --type ts -B 2 -A 5Repository: AOSSIE-Org/OrgExplorer Length of output: 2900 🏁 Script executed: #!/bin/bash
# Check how repos are loaded from GitHub API or localStorage
echo "=== Search for mergeRepos and repo initialization ==="
rg -n "mergeRepos|from.*api|response.*repo" --type ts -B 2 -A 3 | head -80
echo -e "\n=== Check if there are any type assertions bypassing the error ==="
rg -n "as Repo|as any|unknown" --type ts | grep -i repoRepository: AOSSIE-Org/OrgExplorer Length of output: 1327 🏁 Script executed: #!/bin/bash
# Check fetchOrgRepos to understand what data is returned from GitHub API
echo "=== Check fetchOrgRepos implementation ==="
rg -n "fetchOrgRepos|export const fetch" src/services/githubService.ts -A 15
echo -e "\n=== Check if repos property in state/storage includes the URL ==="
rg -n "repos\[|repos\.map|repos\.forEach" --type ts -B 1 -A 1 | head -40
echo -e "\n=== Look at the entire NetworkGraph file to understand repo structure ==="
head -50 src/components/Graph/NetworkGraph.tsxRepository: AOSSIE-Org/OrgExplorer Length of output: 2573 🏁 Script executed: #!/bin/bash
# Check tsconfig.json completely
echo "=== Full tsconfig.json ==="
cat tsconfig.json
echo -e "\n=== Verify what GitHub API actually returns for /orgs/{org}/repos ==="
# Check GitHub API documentation or examples in the code
rg -n "github.com/api" --type ts -B 2 -A 2
echo -e "\n=== Check if there's any documentation or comments about repo structure ==="
rg -n "repo\.|Repo|Repository" src/types/github.ts -B 2 -A 2Repository: AOSSIE-Org/OrgExplorer Length of output: 613 🏁 Script executed: #!/bin/bash
# Check tsconfig.app.json for strict settings
echo "=== Check tsconfig.app.json ==="
cat tsconfig.app.json | head -30
echo -e "\n=== Search GitHub API documentation or comments about contributors_url ==="
rg -n "contributors" --type ts -B 2 -A 2
echo -e "\n=== Verify how repos are cast/stored ==="
rg -n "Repo\[\]|as Repo" --type ts -B 1 -A 1Repository: AOSSIE-Org/OrgExplorer Length of output: 2842 🏁 Script executed: #!/bin/bash
# Check mergeRepos implementation to see if it preserves contributors_url
echo "=== Full mergeRepos implementation ==="
cat src/utils/mergeOrgs.ts
echo -e "\n=== Check what's being stored to localStorage ==="
rg -n "localStorage.setItem.*repos" --type ts -B 2 -A 2Repository: AOSSIE-Org/OrgExplorer Length of output: 710 🏁 Script executed: #!/bin/bash
# Verify the actual GitHub API response contains contributors_url
echo "=== Check if GitHub API docs or examples show contributors_url ==="
web_search "GitHub API /orgs/{org}/repos response schema contributors_url"
echo -e "\n=== Verify if there are any TypeScript errors being suppressed ==="
rg -n "@ts-ignore|@ts-expect-error" --type ts
echo -e "\n=== Check if this code actually compiles ==="
# Look for build errors or how the code is handled
ls -la | grep -E "build|dist|.next"
</parameter>Repository: AOSSIE-Org/OrgExplorer Length of output: 420 🌐 Web query:
💡 Result:
Shape (high-level)Top-level fields commonly include (non-exhaustive):
Formal schema (machine-readable)For an actual OpenAPI schema (rather than an example), use GitHub’s official OpenAPI bundle in Citations:
🌐 Web query:
💡 Result: In the GitHub REST API repository JSON, the Example value (from a repository object payload):
Use it by sending a References: 🏁 Script executed: #!/bin/bash
# Verify if TypeScript catches this as an error
echo "=== Check if repos loaded from localStorage are properly typed ==="
cat src/pages/Repositories.tsx
echo -e "\n=== Check if repos from localStorage ever lack contributors_url ==="
rg -n "JSON.parse.*repos|localStorage.getItem.*repos" --type ts -B 1 -A 3
echo -e "\n=== Verify the actual type flow: API response → mergeRepos → Repo[] ==="
echo "Check Overview.tsx lines 53-60:"
sed -n '50,65p' src/pages/Overview.tsxRepository: AOSSIE-Org/OrgExplorer Length of output: 2358 🏁 Script executed: #!/bin/bash
# Check if this is actually causing a TypeScript compilation error
echo "=== Check for any build output or error logs ==="
find . -name "*.tsbuildinfo" -o -name ".tsc-errors" 2>/dev/null | head -5
echo -e "\n=== Verify if the type error is caught by looking at how repos flow through mergeRepos ==="
# The issue: fetchOrgRepos returns raw API response (which HAS contributors_url)
# but it's typed as Repo[] (which doesn't have contributors_url)
rg -n "allRepos\[|repo\.|Repo" src/utils/mergeOrgs.ts
echo -e "\n=== Check if there's a real issue: does repo.contributors_url exist at runtime? ==="
# If mergeRepos receives raw API responses and just stores them as-is,
# then contributors_url WILL exist at runtime despite the type mismatch
echo "Checking what mergeRepos actually does with the raw data:"
cat src/utils/mergeOrgs.tsRepository: AOSSIE-Org/OrgExplorer Length of output: 892 Add The Update export interface Repo {
id: number;
name: string;
stargazers_count: number;
forks_count: number;
updated_at: string;
contributors_url: string; // Add this field
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| contributors.slice(0, 5).forEach((c: any) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!addedUsers.has(c.login)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| addedUsers.add(c.login); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nodes.push({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: c.login, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type: "user", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| contributions: c.contributions, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| avatar: c.avatar_url | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| links.push({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source: c.login, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target: repo.name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| weight: c.contributions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setGraphData({ nodes, links }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (repos.length) buildGraph(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [repos]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // FORCE SETTINGS | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!fgRef.current) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fgRef.current.d3Force("center", d3.forceCenter(0, 0)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fgRef.current.d3Force("charge", d3.forceManyBody().strength(-100)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fgRef.current.d3Force("link", d3.forceLink().distance(70)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fgRef.current.d3Force( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "radial", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| d3.forceRadial((node: any) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return node.type === "repo" ? 80 : 180; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }).strength(0.8) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [graphData]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!fgRef.current) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // trigger render after mount | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setTimeout(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fgRef.current.zoomToFit(400); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, 300); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [graphData]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Merge duplicate Two separate ♻️ Proposed fix // FORCE SETTINGS
useEffect(() => {
if (!fgRef.current) return;
fgRef.current.d3Force("center", d3.forceCenter(0, 0));
fgRef.current.d3Force("charge", d3.forceManyBody().strength(-100));
fgRef.current.d3Force("link", d3.forceLink().distance(70));
fgRef.current.d3Force(
"radial",
d3.forceRadial((node: any) => {
return node.type === "repo" ? 80 : 180;
}).strength(0.8)
);
+ // Trigger render after mount
+ setTimeout(() => {
+ fgRef.current?.zoomToFit(400);
+ }, 300);
}, [graphData]);
- useEffect(() => {
- if (!fgRef.current) return;
-
- // trigger render after mount
- setTimeout(() => {
- fgRef.current.zoomToFit(400);
- }, 300);
-}, [graphData]);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="h-[650px] relative overflow-hidden bg-[#020617] rounded-xl mt-6 border border-gray-800"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ForceGraph2D | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ref={fgRef} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| graphData={graphData} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| backgroundColor="#020617" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enableZoomInteraction={true} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enablePanInteraction={true} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enableNodeDrag={false} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| autoPauseRedraw={false} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| minZoom={0.5} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maxZoom={2} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onZoomEnd={({k, x, y}) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const limit = 200; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if(Math.abs(x) > limit || Math.abs(y) > limit) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fgRef.current.centerAt(0, 0, 400); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // AUTO CENTER + FREEZE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onEngineStop={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fgRef.current.centerAt(0, 0, 400); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fgRef.current?.zoomToFit(400); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| graphData.nodes.forEach((node: any) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| node.fx = node.x; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| node.fy = node.y; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fgRef.current?.pauseAnimation(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| d3VelocityDecay={0.3} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| d3AlphaDecay={0.02} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cooldownTicks={100} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| warmupTicks={100} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TOOLTIP | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nodeLabel={(node: any) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| node.type === "repo" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? `📦 ${node.id} (⭐ ${node.stars})` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : `👤 ${node.id} (${node.contributions})` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // NODE RENDER (FIXED AVATAR) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nodeCanvasObject={(node: any, ctx, globalScale) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const size = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| node.type === "repo" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? 10 + Math.log(node.stars + 1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : 5; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fontSize = 9 / globalScale; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (node.type === "user" && node.avatar) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let img = imageCache.current[node.avatar]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!img) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| img = new Image(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| img.src = node.avatar; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| imageCache.current[node.avatar] = img; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.save(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.beginPath(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.arc(node.x, node.y, size, 0, 2 * Math.PI); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.closePath(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.clip(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (img.complete) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.drawImage( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| img, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| node.x - size, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| node.y - size, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size * 2, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size * 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.restore(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+136
to
+146
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid allocating This callback runs frequently; creating images per frame is a hot-path performance issue. Cache images (e.g., 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // white border | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.beginPath(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.arc(node.x, node.y, size, 0, 2 * Math.PI); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.strokeStyle = "#ffffff"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.lineWidth = 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.stroke(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // repo node | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.beginPath(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.arc(node.x, node.y, size, 0, 2 * Math.PI); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.fillStyle = "#22c55e"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.fill(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // label | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.font = `${fontSize}px Inter`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.fillStyle = "#e2e8f0"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctx.fillText(node.id, node.x + size + 2, node.y + size / 2); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // EDGE DESIGN (VISIBLE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| linkWidth={(link: any) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Math.max(1, Math.log(link.weight + 1)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| linkColor={() => "rgba(200,200,200,0.6)"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| linkDirectionalParticles={1} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| linkDirectionalParticleSpeed={0.002} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // HOVER | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onNodeHover={(node) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| document.body.style.cursor = node ? "pointer" : "default"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cursor style side effect is not cleaned up. When the component unmounts while hovering over a node, 🛡️ Proposed fix: add useEffect cleanup+ useEffect(() => {
+ return () => {
+ document.body.style.cursor = "default";
+ };
+ }, []);Add this cleanup effect to reset the cursor on unmount. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| export default function HealthScore({ score, label }: any) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -name "HealthScore.tsx" -type fRepository: AOSSIE-Org/OrgExplorer Length of output: 98 🏁 Script executed: cat -n ./src/components/HealthScore.tsxRepository: AOSSIE-Org/OrgExplorer Length of output: 1045 🏁 Script executed: head -20 ./package.json | grep -E "(next|react)" || cat ./next.config.js 2>/dev/null || echo "Checking project type..."Repository: AOSSIE-Org/OrgExplorer Length of output: 182 🏁 Script executed: cat ./package.json | grep -E '"(next|nextjs)"' || ls -la | grep -E "(next\.config|tsconfig)"Repository: AOSSIE-Org/OrgExplorer Length of output: 269 🏁 Script executed: cat ./tsconfig.jsonRepository: AOSSIE-Org/OrgExplorer Length of output: 184 🏁 Script executed: cat ./tsconfig.app.jsonRepository: AOSSIE-Org/OrgExplorer Length of output: 797 🏁 Script executed: find . -name "*i18n*" -o -name "*lang*" -o -name "*translation*" -o -name "*.en.json" | head -20Repository: AOSSIE-Org/OrgExplorer Length of output: 48 Define explicit types for props and externalize hardcoded user-visible strings. This component violates two coding guidelines:
Proposed fix+interface HealthScoreProps {
+ score: number;
+ label: string;
+}
+
-export default function HealthScore({ score, label }: any) {
+export default function HealthScore({ score, label }: HealthScoreProps) {
return (
<div className="bg-[`#111827`] p-5 rounded-xl border border-gray-800 w-full mt-10">
<h3 className="text-gray-400 text-sm mb-2">
- Organization Health Score
+ {t('components.healthScore.title')}
</h3>🤖 Prompt for AI Agents |
||
| return ( | ||
| <div className="bg-[#111827] p-5 rounded-xl border border-gray-800 w-full mt-10"> | ||
|
|
||
| <h3 className="text-gray-400 text-sm mb-2"> | ||
| Organization Health Score | ||
| </h3> | ||
|
Comment on lines
+5
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Externalize section title for i18n.
As per coding guidelines, "Internationalization: User-visible strings should be externalized to resource files (i18n)". 🤖 Prompt for AI Agents |
||
|
|
||
| <div className="flex items-center justify-between mb-3"> | ||
| <span className="text-3xl font-bold text-green-400"> | ||
| {score}/100 | ||
| </span> | ||
| <span className="text-sm text-gray-300"> | ||
| {label} | ||
| </span> | ||
| </div> | ||
|
|
||
| {/* Progress bar */} | ||
| <div className="w-full bg-gray-700 h-3 rounded-full"> | ||
| <div | ||
| className="h-3 rounded-full bg-green-500 transition-all duration-500" | ||
| style={{ width: `${score}%` }} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard progress width with a bounded score value. Use a clamped value to avoid invalid/overflowing width styles. Proposed fix+ const safeScore = Math.max(0, Math.min(100, score));
return (
@@
- style={{ width: `${score}%` }}
+ style={{ width: `${safeScore}%` }}🤖 Prompt for AI Agents |
||
| ></div> | ||
| </div> | ||
|
|
||
| </div> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid
anytype; use explicit types for props and state.Per coding guidelines, avoid
any. Thereposprop and state variables should be explicitly typed.🔧 Proposed fix
As per coding guidelines: "Avoid 'any', use explicit types".
🤖 Prompt for AI Agents