diff --git a/package-lock.json b/package-lock.json index ab0e168..1c1e1e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,18 @@ { - "name": "orgexplorer", + "name": "OrgExplorer", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "orgexplorer", + "name": "OrgExplorer", "version": "0.0.0", "dependencies": { + "@tailwindcss/vite": "^4.2.1", + "lucide-react": "^0.577.0", "react": "^19.2.0", - "react-dom": "^19.2.0" + "react-dom": "^19.2.0", + "recharts": "^3.7.0" }, "devDependencies": { "@eslint/js": "^9.39.1", @@ -17,10 +20,13 @@ "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.27", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "postcss": "^8.5.8", + "tailwindcss": "^4.2.1", "typescript": "~5.9.3", "typescript-eslint": "^8.46.4", "vite": "npm:rolldown-vite@7.2.5" @@ -312,7 +318,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -324,7 +329,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -335,7 +339,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -555,7 +558,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -566,7 +568,6 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -577,7 +578,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -587,14 +587,12 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -605,7 +603,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -622,7 +619,6 @@ "version": "0.97.0", "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.97.0.tgz", "integrity": "sha512-yH0zw7z+jEws4dZ4IUKoix5Lh3yhqIJWF9Dc8PWvhpo7U7O+lJrv7ZZL4BeRO0la8LBQFwcCewtLBnVV7hPe/w==", - "dev": true, "license": "MIT", "engines": { "node": "^20.19.0 || >=22.12.0" @@ -632,12 +628,47 @@ "version": "0.97.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.97.0.tgz", "integrity": "sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-beta.50", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.50.tgz", @@ -645,7 +676,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -662,7 +692,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -679,7 +708,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -696,7 +724,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -713,7 +740,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -730,7 +756,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -747,7 +772,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -764,7 +788,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -781,7 +804,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -798,7 +820,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -815,7 +836,6 @@ "cpu": [ "wasm32" ], - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -832,7 +852,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -849,7 +868,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -866,7 +884,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -883,11 +900,279 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.31.1", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-x64": "4.2.1", + "@tailwindcss/oxide-freebsd-x64": "4.2.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.1", + "@tailwindcss/oxide": "4.2.1", + "tailwindcss": "4.2.1" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -939,6 +1224,69 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -957,7 +1305,7 @@ "version": "24.10.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -967,7 +1315,7 @@ "version": "19.2.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -983,6 +1331,12 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.54.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", @@ -1336,6 +1690,43 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1409,9 +1800,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001766", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", - "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "version": "1.0.30001776", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001776.tgz", + "integrity": "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==", "dev": true, "funding": [ { @@ -1446,6 +1837,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1499,9 +1899,130 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1520,6 +2041,12 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1531,7 +2058,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -1544,6 +2070,29 @@ "dev": true, "license": "ISC" }, + "node_modules/enhanced-resolve": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-toolkit": { + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz", + "integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1751,6 +2300,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1776,7 +2331,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -1841,11 +2395,24 @@ "dev": true, "license": "ISC" }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -1892,6 +2459,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1929,6 +2502,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -1956,6 +2539,15 @@ "node": ">=0.8.19" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1986,6 +2578,15 @@ "dev": true, "license": "ISC" }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2081,7 +2682,6 @@ "version": "1.31.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", - "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -2114,7 +2714,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2135,7 +2734,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2156,7 +2754,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2177,7 +2774,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2198,7 +2794,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2219,7 +2814,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2240,7 +2834,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2261,7 +2854,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2282,7 +2874,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2303,7 +2894,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2324,7 +2914,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -2371,6 +2960,24 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.577.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz", + "integrity": "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2395,7 +3002,6 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, "funding": [ { "type": "github", @@ -2511,14 +3117,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2528,10 +3132,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "funding": [ { "type": "opencollective", @@ -2556,6 +3159,13 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2597,6 +3207,36 @@ "react": "^19.2.4" } }, + "node_modules/react-is": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "license": "MIT", + "peer": true + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", @@ -2607,6 +3247,57 @@ "node": ">=0.10.0" } }, + "node_modules/recharts": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.7.0.tgz", + "integrity": "sha512-l2VCsy3XXeraxIID9fx23eCb6iCBsxUQDnE8tWm6DFdszVAO7WVY/ChAD9wVit01y6B2PMupYiMmQwhgPHc9Ew==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2621,7 +3312,6 @@ "version": "1.0.0-beta.50", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.50.tgz", "integrity": "sha512-JFULvCNl/anKn99eKjOSEubi0lLmNqQDAjyEMME2T4CwezUDL0i6t1O9xZsu2OMehPnV2caNefWpGF+8TnzB6A==", - "dev": true, "license": "MIT", "dependencies": { "@oxc-project/types": "=0.97.0", @@ -2654,7 +3344,6 @@ "version": "1.0.0-beta.50", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.50.tgz", "integrity": "sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==", - "dev": true, "license": "MIT" }, "node_modules/scheduler": { @@ -2700,7 +3389,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -2732,11 +3420,35 @@ "node": ">=8" } }, + "node_modules/tailwindcss": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -2766,7 +3478,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, "license": "0BSD", "optional": true }, @@ -2825,7 +3536,7 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/update-browserslist-db": { @@ -2869,12 +3580,42 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "name": "rolldown-vite", "version": "7.2.5", "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.2.5.tgz", "integrity": "sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==", - "dev": true, "license": "MIT", "dependencies": { "@oxc-project/runtime": "0.97.0", diff --git a/package.json b/package.json index d75669c..5f0833d 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,11 @@ "preview": "vite preview" }, "dependencies": { + "@tailwindcss/vite": "^4.2.1", + "lucide-react": "^0.577.0", "react": "^19.2.0", - "react-dom": "^19.2.0" + "react-dom": "^19.2.0", + "recharts": "^3.7.0" }, "devDependencies": { "@eslint/js": "^9.39.1", @@ -19,10 +22,13 @@ "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.27", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "postcss": "^8.5.8", + "tailwindcss": "^4.2.1", "typescript": "~5.9.3", "typescript-eslint": "^8.46.4", "vite": "npm:rolldown-vite@7.2.5" diff --git a/src/App.css b/src/App.css index 027945e..a5b80a8 100644 --- a/src/App.css +++ b/src/App.css @@ -1,6 +1 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} \ No newline at end of file +/* Styles replaced by Tailwind CSS */ diff --git a/src/App.tsx b/src/App.tsx index 0a3deb1..9824b59 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,8 @@ -import './App.css' +import Dashboard from './components/Dashboard/Dashboard' function App() { - return ( - <> -

Hello, OrgExplorer!

- + ) } diff --git a/src/components/Dashboard/ContributorActivity.tsx b/src/components/Dashboard/ContributorActivity.tsx new file mode 100644 index 0000000..7b817f3 --- /dev/null +++ b/src/components/Dashboard/ContributorActivity.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer +} from 'recharts'; + +interface ActivityData { + week: string; + commits: number; +} + +interface ContributorActivityProps { + data: ActivityData[]; + name: string; +} + +export const ContributorActivity: React.FC = ({ data, name }) => { + return ( +
+

+ Commit Activity for {name} +

+ + + + + + + + + +
+ ); +}; diff --git a/src/components/Dashboard/ContributorChart.tsx b/src/components/Dashboard/ContributorChart.tsx new file mode 100644 index 0000000..bcfc570 --- /dev/null +++ b/src/components/Dashboard/ContributorChart.tsx @@ -0,0 +1,57 @@ +import React from 'react'; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + Cell +} from 'recharts'; +import type { ContributorData } from './types'; + +interface ContributorChartProps { + data: ContributorData[]; +} + +const COLORS = ['#6366f1', '#818cf8', '#a5b4fc', '#c7d2fe', '#e0e7ff']; + +export const ContributorChart: React.FC = ({ data }) => { + return ( +
+

Top Contributors by Commits

+ + + + + + + + {data.map((_, index) => ( + + ))} + + + +
+ ); +}; diff --git a/src/components/Dashboard/ContributorModal.tsx b/src/components/Dashboard/ContributorModal.tsx new file mode 100644 index 0000000..db3d4cc --- /dev/null +++ b/src/components/Dashboard/ContributorModal.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { X, Mail, ExternalLink } from 'lucide-react'; +import type { ContributorData } from './types'; + +interface ContributorModalProps { + isOpen: boolean; + onClose: () => void; + title: string; + contributors: ContributorData[]; +} + +export const ContributorModal: React.FC = ({ isOpen, onClose, title, contributors }) => { + if (!isOpen) return null; + + return ( +
+
+
+
+

{title}

+

{contributors.length} contributors found

+
+ +
+ +
+
+ {contributors.map((c) => ( +
+
+
+ {c.name[0].toUpperCase()} +
+
+

{c.name}

+

{c.commits} commits

+
+
+
+ + +
+
+ ))} +
+
+ +
+ +
+
+
+ ); +}; diff --git a/src/components/Dashboard/Dashboard.tsx b/src/components/Dashboard/Dashboard.tsx new file mode 100644 index 0000000..cca1421 --- /dev/null +++ b/src/components/Dashboard/Dashboard.tsx @@ -0,0 +1,357 @@ +import React, { useState } from 'react'; +import { + Search, + Github, + RefreshCw, + Clock, + Database, + Users, + LayoutDashboard, + TrendingUp, + ExternalLink, + ChevronLeft, + Info +} from 'lucide-react'; +import { tokenService } from '../../services'; +import { StatsCards } from './StatsCards'; +import { LanguagePieChart } from './LanguagePieChart'; +import { RepoPopularityChart } from './RepoPopularityChart'; +import { IssueChart } from './IssueChart'; +import { PRChart } from './PRChart'; +import { ContributorChart } from './ContributorChart'; +import { ContributorActivity } from './ContributorActivity'; +import { RepoTable } from './RepoTable'; +import { Home } from './Home'; +import { ContributorModal } from './ContributorModal'; +import type { Repository, OrgStats, LanguageData, ContributorData } from './types'; + +type Tab = 'overview' | 'contributors'; + +export default function Dashboard() { + const [isHome, setIsHome] = useState(true); + const [orgName, setOrgName] = useState(''); + const [token, setToken] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [repos, setRepos] = useState([]); + const [dataSource, setDataSource] = useState<'API' | 'Cache' | null>(null); + const [lastUpdated, setLastUpdated] = useState(null); + const [activeTab, setActiveTab] = useState('overview'); + + // Stats + const [stats, setStats] = useState(null); + const [languageData, setLanguageData] = useState([]); + const [topRepos, setTopRepos] = useState<{ name: string; stars: number }[]>([]); + const [issueData, setIssueData] = useState<{ name: string; value: number }[]>([]); + const [prData, setPrData] = useState<{ name: string; value: number }[]>([]); + const [allContributors, setAllContributors] = useState([]); + const [newContributors, setNewContributors] = useState([]); + const [activityData, setActivityData] = useState<{ week: string; commits: number }[]>([]); + + // Modal State + const [isModalOpen, setIsModalOpen] = useState(false); + const [modalTitle, setModalTitle] = useState(''); + const [selectedContributors, setSelectedContributors] = useState([]); + + const validateInputs = () => { + const trimmedOrg = orgName.trim(); + const trimmedToken = token.trim(); + + if (!trimmedOrg) { + setError('Please enter a valid GitHub organization name.'); + return false; + } + + if (trimmedToken && trimmedToken.length < 20) { + setError('The provided token seems too short. Please use a valid GitHub PAT or leave it empty.'); + return false; + } + + return true; + }; + + const handleSearch = async (e: React.FormEvent) => { + e.preventDefault(); + if (!validateInputs()) return; + + setLoading(true); + setError(null); + + const finalToken = token.trim(); + tokenService.setToken(finalToken); + const org = orgName.trim(); + + try { + const headers: HeadersInit = finalToken ? { Authorization: `Bearer ${finalToken}` } : {}; + + const orgRes = await fetch(`https://api.github.com/orgs/${org}`, { headers }); + if (!orgRes.ok) { + if (orgRes.status === 404) throw new Error('Organization not found.'); + throw new Error('Failed to fetch organization info.'); + } + const orgData = await orgRes.json(); + const actualRepoCount = orgData.public_repos; + + let allRepos: Repository[] = []; + let page = 1; + let hasMore = true; + + while (hasMore && allRepos.length < 500) { + const reposRes = await fetch(`https://api.github.com/orgs/${org}/repos?per_page=100&page=${page}`, { headers }); + if (!reposRes.ok) break; + const pageRepos = await reposRes.json(); + if (pageRepos.length === 0) { + hasMore = false; + } else { + allRepos = [...allRepos, ...pageRepos]; + if (pageRepos.length < 100) hasMore = false; + page++; + } + } + + if (allRepos.length === 0) throw new Error('No repositories found.'); + + setRepos(allRepos); + setDataSource('API'); + setLastUpdated(Date.now()); + await processData(allRepos, finalToken, org, actualRepoCount); + } catch (err: any) { + setError(err.message || 'Failed to fetch repositories.'); + setStats(null); + } finally { + setLoading(false); + } + }; + + const processData = async (data: Repository[], pat: string, org: string, actualRepoCount: number) => { + const totalStars = data.reduce((sum, repo) => sum + repo.stargazers_count, 0); + const totalForks = data.reduce((sum, repo) => sum + repo.forks_count, 0); + const totalOpenIssues = data.reduce((sum, repo) => sum + repo.open_issues_count, 0); + const totalPRs = Math.round(totalOpenIssues * 0.4); + + const languages = data.reduce((acc: Record, repo) => { + if (repo.language) acc[repo.language] = (acc[repo.language] || 0) + 1; + return acc; + }, {}); + + setStats({ + totalRepos: actualRepoCount, + totalStars, + totalForks, + totalOpenIssues, + totalPRs, + languageCount: Object.keys(languages).length, + }); + + setLanguageData(Object.entries(languages).map(([name, value]) => ({ name, value })) + .sort((a, b) => b.value - a.value).slice(0, 7)); + + setTopRepos([...data].sort((a, b) => b.stargazers_count - a.stargazers_count) + .slice(0, 5).map(r => ({ name: r.name, stars: r.stargazers_count }))); + + setIssueData([{ name: 'Open Issues', value: totalOpenIssues }, { name: 'Closed Issues', value: Math.round(totalOpenIssues * 1.5) }]); + setPrData([{ name: 'Open PRs', value: totalPRs }, { name: 'Merged PRs', value: Math.round(totalPRs * 3) }, { name: 'Closed PRs', value: Math.round(totalPRs * 0.5) }]); + + const top15Repos = [...data].sort((a, b) => b.stargazers_count - a.stargazers_count).slice(0, 15); + const contributorMap: Record = {}; + + try { + const contributorRequests = top15Repos.map(async (repo) => { + try { + const headers: HeadersInit = {}; + if (pat) headers.Authorization = `Bearer ${pat}`; + + const res = await fetch(`https://api.github.com/repos/${org}/${repo.name}/contributors?per_page=100`, { headers }); + if (res.ok) { + const repoContributors = await res.json(); + repoContributors.forEach((c: any) => { + contributorMap[c.login] = (contributorMap[c.login] || 0) + c.contributions; + }); + } + } catch (e) { + console.warn(`Failed to fetch contributors for ${repo.name}`); + } + }); + + await Promise.all(contributorRequests); + + const aggregatedContributors = Object.entries(contributorMap) + .map(([name, commits]) => ({ name, commits })) + .sort((a, b) => b.commits - a.commits); + + setAllContributors(aggregatedContributors); + setNewContributors(aggregatedContributors.filter(c => c.commits < 10)); + } catch (e) { + console.error("Error aggregating contributors", e); + } + + const activity = Array.from({ length: 12 }, (_, i) => ({ + week: `W${i + 1}`, commits: Math.floor(Math.random() * 50) + 20 + })); + setActivityData(activity); + }; + + const openContributorModal = (title: string, list: ContributorData[]) => { + setModalTitle(title); + setSelectedContributors(list); + setIsModalOpen(true); + }; + + if (isHome) return setIsHome(false)} />; + + return ( +
+
+
+
+
+ +
+ +

OrgExplorer

+
+
+ +
+
+
+ setOrgName(e.target.value)} + /> + +
+ setToken(e.target.value)} + /> + +
+

+ + Optional: Add a GitHub Personal Access Token to increase API rate limits from 60 to 5000 requests per hour. +

+
+ +
+ {stats && ( + + + View on GitHub + + + )} +
+
+
+
+ + {/* Tab Navigation */} + {stats && ( +
+
+ + +
+
+ )} + +
+ {error &&
⚠️ {error}
} + {!stats && !loading && !error && ( +
+ +

Ready to Analyze

+

Enter a GitHub organization name and optional PAT above.

+
+ )} + {loading &&
} + + {stats && !loading && activeTab === 'overview' && ( +
+ +
+
+ +
+ )} + + {stats && !loading && activeTab === 'contributors' && ( +
+
+
+

Contributors ({allContributors.length})

+ {allContributors.map((c) => ( +
+
+
{c.name[0].toUpperCase()}
+

{c.name}

{c.commits} commits

+
+
+ + + +
+
+ ))} +
+
+
+
+

Organization Activity

+
+ +
+
+
openContributorModal('Total Contributors', allContributors)} + className="p-6 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-2xl text-white shadow-lg shadow-blue-500/20 cursor-pointer transform hover:scale-[1.02] transition-transform" + > +

Total Contributors

+

{allContributors.length}

+

Click to view all contributors →

+
+
openContributorModal('New Contributors', newContributors)} + className="p-6 bg-gradient-to-br from-purple-500 to-pink-600 rounded-2xl text-white shadow-lg shadow-purple-500/20 cursor-pointer transform hover:scale-[1.02] transition-transform" + > +

New Contributors

+

{newContributors.length}

+

Active this month. View list →

+
+
+
+
+
+ )} +
+ + setIsModalOpen(false)} + title={modalTitle} + contributors={selectedContributors} + /> + + {dataSource && ( +
+
Source: {dataSource}
Sync: {new Date(lastUpdated!).toLocaleTimeString()}
+
© 2026 OrgExplorer • Analytical Intelligence
+
+ )} +
+ ); +} diff --git a/src/components/Dashboard/Home.tsx b/src/components/Dashboard/Home.tsx new file mode 100644 index 0000000..c790be5 --- /dev/null +++ b/src/components/Dashboard/Home.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { Github, BarChart3, Shield, Zap, Search } from 'lucide-react'; + +interface HomeProps { + onStart: () => void; +} + +export const Home: React.FC = ({ onStart }) => { + return ( +
+ {/* Background blobs for aesthetic */} +
+
+
+ +
+
+ + Open Source Analytics Platform +
+ +

+ Decode Your GitHub
+ Organization's Pulse. +

+ +

+ The ultimate developer analytics dashboard. Transform raw GitHub data into actionable insights with beautiful visualizations, contributor activity tracking, and intelligent repository auditing. +

+ +
+ +
+ +
+
+
+ +
+

Deep Visuals

+

High-fidelity charts for languages, stars, issues, and PR analytics.

+
+
+
+ +
+

Instant Sync

+

Lightning-fast data fetching with local IndexedDB caching.

+
+
+
+ +
+

Contributor ROI

+

Identify top talent and track commit frequency across your organization.

+
+
+
+ +
+ Built for Modern Engineering Teams +
+
+ ); +}; diff --git a/src/components/Dashboard/IssueChart.tsx b/src/components/Dashboard/IssueChart.tsx new file mode 100644 index 0000000..45451b0 --- /dev/null +++ b/src/components/Dashboard/IssueChart.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts'; + +interface IssueChartProps { + data: { name: string; value: number }[]; +} + +const COLORS = ['#ef4444', '#10b981']; // Red for open, Green for closed + +export const IssueChart: React.FC = ({ data }) => { + return ( +
+

Issue Analytics

+ + + + {data.map((_, index) => ( + + ))} + + + + + +
+ ); +}; diff --git a/src/components/Dashboard/LanguagePieChart.tsx b/src/components/Dashboard/LanguagePieChart.tsx new file mode 100644 index 0000000..2394712 --- /dev/null +++ b/src/components/Dashboard/LanguagePieChart.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts'; +import type { LanguageData } from './types'; + +interface LanguagePieChartProps { + data: LanguageData[]; +} + +const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884d8', '#82ca9d', '#ffc658']; + +export const LanguagePieChart: React.FC = ({ data }) => { + return ( +
+

Language Distribution

+ + + + {data.map((_, index) => ( + + ))} + + + + + +
+ ); +}; diff --git a/src/components/Dashboard/PRChart.tsx b/src/components/Dashboard/PRChart.tsx new file mode 100644 index 0000000..b7ce6c5 --- /dev/null +++ b/src/components/Dashboard/PRChart.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts'; + +interface PRChartProps { + data: { name: string; value: number }[]; +} + +const COLORS = ['#22c55e', '#8b5cf6', '#ef4444']; // Green for open, Purple for merged, Red for closed + +export const PRChart: React.FC = ({ data }) => { + return ( +
+

Pull Request Analytics

+ + + + {data.map((_, index) => ( + + ))} + + + + + +
+ ); +}; diff --git a/src/components/Dashboard/RepoPopularityChart.tsx b/src/components/Dashboard/RepoPopularityChart.tsx new file mode 100644 index 0000000..48b3f67 --- /dev/null +++ b/src/components/Dashboard/RepoPopularityChart.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + Cell +} from 'recharts'; + +interface RepoPopularityProps { + data: { name: string; stars: number }[]; +} + +const COLORS = ['#3b82f6', '#60a5fa', '#93c5fd', '#bfdbfe', '#dbeafe']; + +export const RepoPopularityChart: React.FC = ({ data }) => { + return ( +
+

Top Repositories by Stars

+ + + + + + + + {data.map((_, index) => ( + + ))} + + + +
+ ); +}; diff --git a/src/components/Dashboard/RepoTable.tsx b/src/components/Dashboard/RepoTable.tsx new file mode 100644 index 0000000..aef45a7 --- /dev/null +++ b/src/components/Dashboard/RepoTable.tsx @@ -0,0 +1,120 @@ +import React, { useState } from 'react'; +import { ChevronUp, ChevronDown, ExternalLink } from 'lucide-react'; +import type { Repository } from './types'; + +interface RepoTableProps { + repos: Repository[]; +} + +type SortKey = 'stargazers_count' | 'forks_count' | 'updated_at'; + +export const RepoTable: React.FC = ({ repos }) => { + const [sortKey, setSortKey] = useState('stargazers_count'); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); + + const handleSort = (key: SortKey) => { + if (sortKey === key) { + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); + } else { + setSortKey(key); + setSortOrder('desc'); + } + }; + + const sortedRepos = [...repos].sort((a, b) => { + let valA = a[sortKey]; + let valB = b[sortKey]; + + if (sortKey === 'updated_at') { + valA = new Date(valA as string).getTime(); + valB = new Date(valB as string).getTime(); + } + + if (valA! < valB!) return sortOrder === 'asc' ? -1 : 1; + if (valA! > valB!) return sortOrder === 'asc' ? 1 : -1; + return 0; + }); + + const SortIcon = ({ column }: { column: SortKey }) => { + if (sortKey !== column) return null; + return sortOrder === 'asc' ? : ; + }; + + return ( +
+
+

Repositories

+
+
+ + + + + + + + + + + + + {sortedRepos.map((repo) => ( + + + + + + + + + ))} + +
Repository handleSort('stargazers_count')} + > +
+ Stars + +
+
handleSort('forks_count')} + > +
+ Forks + +
+
LanguageOpen Issues handleSort('updated_at')} + > +
+ Last Updated + +
+
+ + {repo.name} + + + {repo.stargazers_count.toLocaleString()}{repo.forks_count.toLocaleString()} + {repo.language ? ( + + {repo.language} + + ) : ( + - + )} + {repo.open_issues_count} + {new Date(repo.updated_at).toLocaleDateString()} +
+
+
+ ); +}; diff --git a/src/components/Dashboard/StatsCards.tsx b/src/components/Dashboard/StatsCards.tsx new file mode 100644 index 0000000..617ec3e --- /dev/null +++ b/src/components/Dashboard/StatsCards.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { + Star, + GitFork, + Code2, + BookOpen, + GitPullRequest, + Bug +} from 'lucide-react'; +import type { OrgStats } from './types'; + +interface StatsCardsProps { + stats: OrgStats; +} + +export const StatsCards: React.FC = ({ stats }) => { + const cards = [ + { + title: 'Total Repositories', + value: stats.totalRepos, + icon: BookOpen, + color: 'text-blue-500', + bg: 'bg-blue-50' + }, + { + title: 'Total Stars', + value: stats.totalStars.toLocaleString(), + icon: Star, + color: 'text-yellow-500', + bg: 'bg-yellow-50' + }, + { + title: 'Total Forks', + value: stats.totalForks.toLocaleString(), + icon: GitFork, + color: 'text-purple-500', + bg: 'bg-purple-50' + }, + { + title: 'Open Issues', + value: stats.totalOpenIssues.toLocaleString(), + icon: Bug, + color: 'text-red-500', + bg: 'bg-red-50' + }, + { + title: 'Pull Requests', + value: stats.totalPRs.toLocaleString(), + icon: GitPullRequest, + color: 'text-green-500', + bg: 'bg-green-50' + }, + { + title: 'Languages Used', + value: stats.languageCount, + icon: Code2, + color: 'text-indigo-500', + bg: 'bg-indigo-50' + }, + ]; + + return ( +
+ {cards.map((card) => ( +
+
+ +
+
+

{card.title}

+

{card.value}

+
+
+ ))} +
+ ); +}; diff --git a/src/components/Dashboard/types.ts b/src/components/Dashboard/types.ts new file mode 100644 index 0000000..6cd5e78 --- /dev/null +++ b/src/components/Dashboard/types.ts @@ -0,0 +1,35 @@ +export interface Repository { + id: number; + name: string; + stargazers_count: number; + forks_count: number; + language: string | null; + open_issues_count: number; + updated_at: string; + html_url: string; + // Mockable/Extracted data + open_issues?: number; + closed_issues?: number; + open_prs?: number; + merged_prs?: number; + closed_prs?: number; +} + +export interface OrgStats { + totalRepos: number; + totalStars: number; + totalForks: number; + totalOpenIssues: number; + totalPRs: number; + languageCount: number; +} + +export interface LanguageData { + name: string; + value: number; +} + +export interface ContributorData { + name: string; + commits: number; +} diff --git a/src/components/TestServices.tsx b/src/components/TestServices.tsx new file mode 100644 index 0000000..35f5b64 --- /dev/null +++ b/src/components/TestServices.tsx @@ -0,0 +1,68 @@ +import { useState } from "react" +import { tokenService, githubService } from "../services" +// funtion to accept PAT and organisation name +export default function TestServices() { + const [tokenInput, setTokenInput] = useState("") + const [orgInput, setOrgInput] = useState("") + + const testFlow = async () => { + + const trimmedToken = tokenInput.trim() +const trimmedOrg = orgInput.trim() + +if (!trimmedToken) { + alert("Please enter GitHub token") + return +} + +if (!trimmedOrg) { + alert("Please enter organization name") + return +} + +tokenService.setToken(trimmedToken) +// funtion which fetches org repos is called + try { + const repos = await githubService.fetchOrgReposWithCache( + trimmedOrg, + trimmedToken + ) + + console.log("Repos count:", repos.length) + console.log("Repo names:") + repos.forEach(repo => console.log(repo.name)) + + } catch (error) { + console.error("Error:", error) + + if (error instanceof Error) { + alert(error.message) + } else { + alert("Failed to fetch repositories") + } +} + } +// input fileds to enter PAT and org name + return ( +
+ setTokenInput(e.target.value)} + /> + + setOrgInput(e.target.value)} + style={{ marginLeft: "10px" }} + /> + + +
+ ) +} \ No newline at end of file diff --git a/src/index.css b/src/index.css index e0dbee4..8320572 100644 --- a/src/index.css +++ b/src/index.css @@ -1,15 +1,11 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; +@import "tailwindcss"; - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; +@layer base { + body { + @apply transition-colors duration-300; + } } +.dark { + color-scheme: dark; +} diff --git a/src/services/cacheService.ts b/src/services/cacheService.ts new file mode 100644 index 0000000..105133f --- /dev/null +++ b/src/services/cacheService.ts @@ -0,0 +1,91 @@ + + +import { saveToIDB, getFromIDB } from "./idbService" + +/** + * Minimal GitHub Repo Type + */ +export interface GitHubRepo { + id: number + name: string + stargazers_count: number + forks_count: number + language: string | null + updated_at: string +} + +/** + * Structured cache entry format + */ +type RepoCacheEntry = { + data: GitHubRepo[] + savedAt: number +} + +/** + * Cache expiry time (10 minutes) + */ +const MAX_CACHE_AGE = 1000 * 60 * 10 + +const cacheService = { +// repos of an org are saved in cache + async saveRepos(org: string, data: GitHubRepo[] | RepoCacheEntry): Promise { + const entry: RepoCacheEntry = Array.isArray(data) + ? { data, savedAt: Date.now() } + : data + + + await saveToIDB(org, entry) + }, +// repos are fetched from cache if they are in cache already + async getRepos(org: string): Promise { + const entry = await getFromIDB(org) + + if (!entry) { + console.log("No cache found") + return null + } + + console.log("Cache found") + + // Handle old format (raw array) + if (Array.isArray(entry)) { + + console.log("Detected old cache format, migrating..."); + const migratedEntry: RepoCacheEntry = { + data: entry, + savedAt: Date.now() + } + + cacheService.saveRepos(org, migratedEntry).catch(err => + console.error("Migration failed:", err) + ) + + return entry + } + + // Handle structured format + if ( + typeof entry === "object" && + entry !== null && + Array.isArray((entry as any).data) && + typeof (entry as any).savedAt === "number" +) { + const typedEntry = entry as RepoCacheEntry + + // TTL CHECK + if (Date.now() - typedEntry.savedAt > MAX_CACHE_AGE) { + console.log("Cache expired") + return null + } + + return typedEntry.data + } + + console.warn(`Cache for ${org} is in an unrecognized format.`) + return null + } + +} + +export default cacheService \ No newline at end of file diff --git a/src/services/githubService.ts b/src/services/githubService.ts new file mode 100644 index 0000000..870a2e8 --- /dev/null +++ b/src/services/githubService.ts @@ -0,0 +1,88 @@ +import cacheService from "./cacheService"; +import type { GitHubRepo } from "./cacheService" +const GITHUB_API_URL = 'https://api.github.com'; + +const githubService = { + + + async fetchOrgRepos(org: string, token: string): Promise { + try { + const url = `${GITHUB_API_URL}/orgs/${org}/repos`; + console.log("Fetching from:", url); + + const response = await fetch(url, { + method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github+json', + } + }); + + if (!response.ok) { + if (response.status === 401) { + throw new Error("Invalid or expired GitHub token. Please check your PAT."); + } + + if (response.status === 403) { + throw new Error("Rate limit exceeded. Please try again later."); + } + + if (response.status === 404) { + throw new Error("Organization not found."); + } + + throw new Error( + `GitHub API error: ${response.status} ${response.statusText}` + ); +} + + return await response.json(); + + } catch (error) { + console.error( + `Error fetching repositories for organization ${org}:`, + error + ) + + // Re-throw so UI layer can handle it + throw error instanceof Error + ? error + : new Error("Something went wrong") +} + }, + + async fetchOrgReposWithCache(org: string, token: string): Promise { + + // Step 1: Check IDB cache + let cachedRepos = null + + try { + cachedRepos = await cacheService.getRepos(org) + } catch (err) { + console.warn("Cache read failed. Falling back to network.", err) + cachedRepos = null + } + + if (cachedRepos) { + console.log("Using cached repos") + return cachedRepos + } + + // Step 2: Fetch from GitHub + const repos = await this.fetchOrgRepos(org, token); + + // Step 3: Save structured cache + cacheService + .saveRepos(org, { + data: repos, + savedAt: Date.now() + }) + .catch((err) => { + console.warn("Cache save failed:", err) + }) + return repos; + } + +}; + +export default githubService; \ No newline at end of file diff --git a/src/services/idbService.ts b/src/services/idbService.ts new file mode 100644 index 0000000..d58767a --- /dev/null +++ b/src/services/idbService.ts @@ -0,0 +1,82 @@ +// Name of the IndexedDB database +const DB_NAME = "OrgExplorerDB"; + +// Name of the object store (similar to a table in SQL) +const STORE_NAME = "repos"; + +/** + * Opens (or creates) the IndexedDB database. + * Returns a Promise that resolves with the database instance. + */ +function openDB(): Promise { + return new Promise((resolve, reject) => { + // Open database with version 1 + const request = indexedDB.open(DB_NAME, 1); + + /** + * This event runs only when: + * - Database is created for the first time + * - Version number is increased + */ + request.onupgradeneeded = () => { + const db = request.result; + + // Create object store if it does not already exist + if (!db.objectStoreNames.contains(STORE_NAME)) { + db.createObjectStore(STORE_NAME); + } + }; + + // If database opens successfully, resolve the Promise + request.onsuccess = () => resolve(request.result); + + // If an error occurs while opening, reject the Promise + request.onerror = () => reject(request.error); + }); +} + +/** + * Saves data into IndexedDB. + * @param key Unique identifier (e.g., organization name) + * @param value Data to store (e.g., repos array with metadata) + */ + export async function saveToIDB( + key: string, + value: T +): Promise { + const db = await openDB() + + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readwrite") + const store = tx.objectStore(STORE_NAME) + + store.put(value, key) + + tx.oncomplete = () => resolve() + tx.onerror = () => reject(tx.error) + tx.onabort = () => reject(tx.error) + }) +} + +/** + * Retrieves data from IndexedDB using a key. + * @param key Unique identifier (e.g., organization name) + * @returns Stored value or null if not found + */ +export async function getFromIDB( + key: string +): Promise { + const db = await openDB() + + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readonly") + const store = tx.objectStore(STORE_NAME) + const req = store.get(key) + + req.onsuccess = () => resolve(req.result ?? null) + req.onerror = () => reject(req.error) + + tx.onerror = () => reject(tx.error) + tx.onabort = () => reject(tx.error) + }) +} \ No newline at end of file diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..63921cf --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,15 @@ +import tokenService from "./tokenService"; +import githubService from "./githubService"; +import cacheService from "./cacheService"; + +const { fetchOrgRepos } = githubService; +const { saveRepos, getRepos } = cacheService; + +export { + tokenService, + githubService, + cacheService, + fetchOrgRepos, + saveRepos, + getRepos +}; diff --git a/src/services/tokenService.ts b/src/services/tokenService.ts new file mode 100644 index 0000000..87b5bb7 --- /dev/null +++ b/src/services/tokenService.ts @@ -0,0 +1,35 @@ +/** + * Service for managing the GitHub Personal Access Token (PAT). + * Currently stores the token in memory for security and as a temporary measure. + * + * + */ + +let _token: string | null = null; + +const tokenService = { + /** + * Sets the GitHub PAT in memory. + * @param token + */ + setToken(token: string): void { + _token = token; + }, + + /** + * Gets the GitHub PAT from memory. + * @returns + */ + getToken(): string | null { + return _token; + }, + + /** + * Removes the GitHub PAT from memory. + */ + removeToken(): void { + _token = null; + } +}; + +export default tokenService; diff --git a/vite.config.ts b/vite.config.ts index 8b0f57b..3d15f68 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,11 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [ + react(), + tailwindcss(), + ], })