This commit is contained in:
BhagwanSingh 2025-11-10 12:27:22 +05:30
commit bb8b654e93
15 changed files with 457 additions and 198 deletions

View File

@ -5,10 +5,10 @@
<!-- <link rel="icon" type="image/svg+xml" href="" /> --> <!-- <link rel="icon" type="image/svg+xml" href="" /> -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>VA Tracker</title> <title>VA Tracker</title>
<meta <!-- <meta
http-equiv="Content-Security-Policy" http-equiv="Content-Security-Policy"
content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self';" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self';"
/> /> -->
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

182
package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "va-tracker", "name": "va-tracker",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^2.10.1",
"@tailwindcss/vite": "^4.1.16", "@tailwindcss/vite": "^4.1.16",
"axios": "^1.13.2", "axios": "^1.13.2",
"chalk": "^5.6.2", "chalk": "^5.6.2",
@ -17,6 +18,7 @@
"os-utils": "^0.0.14", "os-utils": "^0.0.14",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-redux": "^9.2.0",
"react-router-dom": "^7.9.5", "react-router-dom": "^7.9.5",
"recharts": "^2.12.7", "recharts": "^2.12.7",
"sharp": "^0.34.4", "sharp": "^0.34.4",
@ -79,6 +81,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz",
"integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.0", "@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.2", "@babel/code-frame": "^7.24.2",
@ -2068,6 +2071,32 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@reduxjs/toolkit": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.10.1.tgz",
"integrity": "sha512-/U17EXQ9Do9Yx4DlNGU6eVNfZvFJfYpUtRRdLf19PbPjdWBxNlxGZXywQZ1p1Nz8nMkWplTI7iD/23m07nolDA==",
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@standard-schema/utils": "^0.3.0",
"immer": "^10.2.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/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.22.4", "version": "4.22.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz",
@ -2272,6 +2301,18 @@
"url": "https://github.com/sindresorhus/is?sponsor=1" "url": "https://github.com/sindresorhus/is?sponsor=1"
} }
}, },
"node_modules/@standard-schema/spec": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
"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/@szmarczak/http-timer": { "node_modules/@szmarczak/http-timer": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
@ -2747,13 +2788,14 @@
"version": "15.7.12", "version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
"dev": true "devOptional": true
}, },
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "18.3.8", "version": "18.3.8",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.8.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.8.tgz",
"integrity": "sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==", "integrity": "sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==",
"dev": true, "devOptional": true,
"peer": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.0.2" "csstype": "^3.0.2"
@ -2777,6 +2819,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"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/@types/verror": { "node_modules/@types/verror": {
"version": "1.10.10", "version": "1.10.10",
"resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.10.tgz", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.10.tgz",
@ -2983,6 +3031,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@ -3171,7 +3220,6 @@
"resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz",
"integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"archiver-utils": "^2.1.0", "archiver-utils": "^2.1.0",
"async": "^3.2.4", "async": "^3.2.4",
@ -3190,7 +3238,6 @@
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz",
"integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"glob": "^7.1.4", "glob": "^7.1.4",
"graceful-fs": "^4.2.0", "graceful-fs": "^4.2.0",
@ -3212,7 +3259,6 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"core-util-is": "~1.0.0", "core-util-is": "~1.0.0",
"inherits": "~2.0.3", "inherits": "~2.0.3",
@ -3227,15 +3273,13 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/archiver-utils/node_modules/string_decoder": { "node_modules/archiver-utils/node_modules/string_decoder": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"safe-buffer": "~5.1.0" "safe-buffer": "~5.1.0"
} }
@ -3479,6 +3523,7 @@
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ],
"peer": true,
"dependencies": { "dependencies": {
"caniuse-lite": "^1.0.30001587", "caniuse-lite": "^1.0.30001587",
"electron-to-chromium": "^1.4.668", "electron-to-chromium": "^1.4.668",
@ -4049,7 +4094,6 @@
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
"integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"buffer-crc32": "^0.2.13", "buffer-crc32": "^0.2.13",
"crc32-stream": "^4.0.2", "crc32-stream": "^4.0.2",
@ -4231,7 +4275,6 @@
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"dev": true, "dev": true,
"peer": true,
"bin": { "bin": {
"crc32": "bin/crc32.njs" "crc32": "bin/crc32.njs"
}, },
@ -4244,7 +4287,6 @@
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz",
"integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"crc-32": "^1.2.0", "crc-32": "^1.2.0",
"readable-stream": "^3.4.0" "readable-stream": "^3.4.0"
@ -4656,6 +4698,7 @@
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-25.0.5.tgz", "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-25.0.5.tgz",
"integrity": "sha512-ocnZV44ZqInoSFaY54fF7BlCtw+WtbrjyPrkBhaB+Ztn7GPKjmFgRbIKytifJ8h9Cib8jdFRMgjCUtkU45Y6DA==", "integrity": "sha512-ocnZV44ZqInoSFaY54fF7BlCtw+WtbrjyPrkBhaB+Ztn7GPKjmFgRbIKytifJ8h9Cib8jdFRMgjCUtkU45Y6DA==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"app-builder-lib": "25.0.5", "app-builder-lib": "25.0.5",
"builder-util": "25.0.3", "builder-util": "25.0.3",
@ -4879,7 +4922,6 @@
"resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-25.0.5.tgz", "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-25.0.5.tgz",
"integrity": "sha512-N2U7LGSdt4hmEhjEeIV2XJbjj2YIrTL6enfsGKfOhGTpL6GEejUmT3gjdKUqKBS5+NBx0GWhnEwD3MpO2P6Nfg==", "integrity": "sha512-N2U7LGSdt4hmEhjEeIV2XJbjj2YIrTL6enfsGKfOhGTpL6GEejUmT3gjdKUqKBS5+NBx0GWhnEwD3MpO2P6Nfg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"app-builder-lib": "25.0.5", "app-builder-lib": "25.0.5",
"archiver": "^5.3.1", "archiver": "^5.3.1",
@ -4892,7 +4934,6 @@
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"graceful-fs": "^4.2.0", "graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1", "jsonfile": "^6.0.1",
@ -4907,7 +4948,6 @@
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"universalify": "^2.0.0" "universalify": "^2.0.0"
}, },
@ -4920,7 +4960,6 @@
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
@ -5202,15 +5241,6 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"devOptional": true "devOptional": true
}, },
"node_modules/encoding": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
"optional": true,
"dependencies": {
"iconv-lite": "^0.6.2"
}
},
"node_modules/end-of-stream": { "node_modules/end-of-stream": {
"version": "1.4.4", "version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@ -5633,8 +5663,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/fs-extra": { "node_modules/fs-extra": {
"version": "8.1.0", "version": "8.1.0",
@ -6540,6 +6569,16 @@
} }
] ]
}, },
"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/imurmurhash": { "node_modules/imurmurhash": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@ -6887,8 +6926,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/isbinaryfile": { "node_modules/isbinaryfile": {
"version": "5.0.2", "version": "5.0.2",
@ -7149,7 +7187,6 @@
"resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
"integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"readable-stream": "^2.0.5" "readable-stream": "^2.0.5"
}, },
@ -7162,7 +7199,6 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"core-util-is": "~1.0.0", "core-util-is": "~1.0.0",
"inherits": "~2.0.3", "inherits": "~2.0.3",
@ -7177,15 +7213,13 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/lazystream/node_modules/string_decoder": { "node_modules/lazystream/node_modules/string_decoder": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"safe-buffer": "~5.1.0" "safe-buffer": "~5.1.0"
} }
@ -7463,36 +7497,31 @@
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/lodash.difference": { "node_modules/lodash.difference": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
"integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/lodash.flatten": { "node_modules/lodash.flatten": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
"integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/lodash.isplainobject": { "node_modules/lodash.isplainobject": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/lodash.union": { "node_modules/lodash.union": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
"integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/log-symbols": { "node_modules/log-symbols": {
"version": "4.1.0", "version": "4.1.0",
@ -8132,7 +8161,6 @@
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -8757,8 +8785,7 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/progress": { "node_modules/progress": {
"version": "2.0.3", "version": "2.0.3",
@ -8839,6 +8866,7 @@
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
}, },
@ -8850,6 +8878,7 @@
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"scheduler": "^0.23.2" "scheduler": "^0.23.2"
@ -8863,6 +8892,30 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"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",
"peer": true,
"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": { "node_modules/react-refresh": {
"version": "0.14.2", "version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
@ -9001,7 +9054,6 @@
"resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
"integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"minimatch": "^5.1.0" "minimatch": "^5.1.0"
} }
@ -9011,7 +9063,6 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.1"
}, },
@ -9049,6 +9100,22 @@
"decimal.js-light": "^2.4.1" "decimal.js-light": "^2.4.1"
} }
}, },
"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",
"peer": true
},
"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/regenerator-runtime": { "node_modules/regenerator-runtime": {
"version": "0.14.1", "version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
@ -9107,6 +9174,12 @@
"url": "https://github.com/sponsors/jet2jet" "url": "https://github.com/sponsors/jet2jet"
} }
}, },
"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": { "node_modules/resolve": {
"version": "1.22.8", "version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@ -9967,7 +10040,6 @@
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"bl": "^4.0.3", "bl": "^4.0.3",
"end-of-stream": "^1.4.1", "end-of-stream": "^1.4.1",
@ -10337,6 +10409,15 @@
"punycode": "^2.1.0" "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/utf8-byte-length": { "node_modules/utf8-byte-length": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
@ -10399,6 +10480,7 @@
"version": "5.4.7", "version": "5.4.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz",
"integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==", "integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.43", "postcss": "^8.4.43",
@ -10831,7 +10913,6 @@
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz",
"integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"archiver-utils": "^3.0.4", "archiver-utils": "^3.0.4",
"compress-commons": "^4.1.2", "compress-commons": "^4.1.2",
@ -10846,7 +10927,6 @@
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz",
"integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"glob": "^7.2.3", "glob": "^7.2.3",
"graceful-fs": "^4.2.0", "graceful-fs": "^4.2.0",

View File

@ -18,6 +18,7 @@
"test:unit": "vitest src" "test:unit": "vitest src"
}, },
"dependencies": { "dependencies": {
"@reduxjs/toolkit": "^2.10.1",
"@tailwindcss/vite": "^4.1.16", "@tailwindcss/vite": "^4.1.16",
"axios": "^1.13.2", "axios": "^1.13.2",
"chalk": "^5.6.2", "chalk": "^5.6.2",
@ -27,6 +28,7 @@
"os-utils": "^0.0.14", "os-utils": "^0.0.14",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-redux": "^9.2.0",
"react-router-dom": "^7.9.5", "react-router-dom": "^7.9.5",
"recharts": "^2.12.7", "recharts": "^2.12.7",
"sharp": "^0.34.4", "sharp": "^0.34.4",

View File

@ -1,54 +1,55 @@
// electron/ipc/tokenHandler.ts import { ipcMain, BrowserWindow } from "electron";
import { ipcMain } from 'electron'; import { getToken, saveToken, clearToken } from "../utils/tokenHelper.js";
import { getToken, saveToken, clearToken } from '../utils/tokenHelper.js'; import { getActiveApp, handleLogin, takeScreenshot, startWindowPolling } from "../utils/utilityFunctions.js";
import { getActiveApp, handleLogin, takeScreenshot } from '../utils/utilityFunctions.js'; import api from "../api/axiosClient.js";
import api from '../api/axiosClient.js';
import { getMainWindow } from '../main.js'; import { getMainWindow } from '../main.js';
import { exec } from 'child_process'; import { exec } from 'child_process';
ipcMain.handle('get-token', async (_, TOKEN_KEY: "ACCESS_TOKEN" | "REFRESH_TOKEN") => { export const registerIPCHandlers = (mainWindow: BrowserWindow) => {
return await getToken(TOKEN_KEY); console.log("⚙️ Registering IPC Handlers...");
});
ipcMain.handle('save-token', async (_, TOKEN_KEY: "ACCESS_TOKEN" | "REFRESH_TOKEN", token: string) => { ipcMain.handle("get-token", async (_, key: "ACCESS_TOKEN" | "REFRESH_TOKEN") => {
await saveToken(TOKEN_KEY, token); return await getToken(key);
});
ipcMain.handle("save-token", async (_, key: "ACCESS_TOKEN" | "REFRESH_TOKEN", token: string) => {
await saveToken(key, token);
return true; return true;
}); });
ipcMain.handle('clear-token', async (_, TOKEN_KEY: string) => { ipcMain.handle("clear-token", async (_, key: string) => {
await clearToken(TOKEN_KEY); await clearToken(key);
return true; return true;
}); });
ipcMain.handle("upload-screenshot", async () => {
await takeScreenshot();
return { success: true };
});
ipcMain.handle('upload-screenshot', async () => { ipcMain.handle("login-request", async (_, credentials: { email: string; password: string }) => {
takeScreenshot()
})
// Handle login request from renderer
ipcMain.handle('login-request', async (event, credentials: { email: string, password: string }) => {
return handleLogin(credentials); return handleLogin(credentials);
}); });
ipcMain.handle('logout-request', async () => { ipcMain.handle("logout-request", async () => {
try { try {
console.log("Handle Logout listened") console.log("Handle Logout listened");
console.log("Token", await getToken("ACCESS_TOKEN")) console.log("Token", await getToken("ACCESS_TOKEN"));
const res = await api.get("/logout"); const res = await api.get("/logout");
if (res && ("success" in res) && res.success) { if (res && "success" in res && res.success) {
return { success: true, message: "Logout Successfully" } return { success: true, message: "Logout Successfully" };
} }
return { success: false, message: "" } return { success: false, message: "" };
} catch (error) { } catch {
return { success: false, message: "" } return { success: false, message: "Logout failed" };
} }
}); });
ipcMain.handle('get-active-app', () => { ipcMain.handle("get-active-app", async () => {
return getActiveApp(); return await getActiveApp();
}); });
ipcMain.handle('toggle-close-btn', (event, shouldEnable: boolean) => { ipcMain.handle('toggle-close-btn', (event, shouldEnable: boolean) => {
const mainWindow = getMainWindow() const mainWindow = getMainWindow()
console.log("toggle-close-btn Called", mainWindow) console.log("toggle-close-btn Called", mainWindow)
if (mainWindow && !mainWindow.isDestroyed()) { if (mainWindow && !mainWindow.isDestroyed()) {
@ -59,26 +60,11 @@ ipcMain.handle('toggle-close-btn', (event, shouldEnable: boolean) => {
ipcMain.handle('resize-window', (event, width: number, height: number) => { ipcMain.handle('resize-window', (event, width: number, height: number) => {
const mainWindow = getMainWindow() const mainWindow = getMainWindow()
console.log("toggle-close-btn Called", mainWindow) console.log("toggle-close-btn Called", mainWindow)
if (mainWindow && !mainWindow.isDestroyed()) { // if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.setSize(width, height); mainWindow.setSize(width, height);
} // }
}); })
ipcMain.handle('get-running-apps', async () => { // ✅ Start background polling when main window is ready
return new Promise((resolve, reject) => { };
exec('tasklist', (err, stdout) => { // startWindowPolling(mainWindow);
if (err) return reject(err);
const lines = stdout.split('\n').slice(3); // skip headers
const apps = lines
.map(line => line.trim().split(/\s+/))
.filter(parts => parts.length > 1)
.map(parts => ({
name: parts[0],
pid: parts[1],
}));
resolve(apps);
});
});
});

View File

@ -4,6 +4,7 @@ import { getStaticData, pollResources } from './resourceManager.js';
import { getPreloadPath, getUIPath } from './pathResolver.js'; import { getPreloadPath, getUIPath } from './pathResolver.js';
import "./ipc/ipcHandler.js"; import "./ipc/ipcHandler.js";
import { registerIPCHandlers } from './ipc/ipcHandler.js';
let mainWindow:any = null; let mainWindow:any = null;
@ -14,7 +15,7 @@ app.on('ready', async () => {
width: 500, // fixed width width: 500, // fixed width
height: 600, // fixed height height: 600, // fixed height
resizable: false, // prevents resizing resizable: true, // prevents resizing
fullscreenable: false, // disables fullscreen toggle (F11 or double-click title bar) fullscreenable: false, // disables fullscreen toggle (F11 or double-click title bar)
minimizable: false, // disables minimize button minimizable: false, // disables minimize button
maximizable: false, // prevents maximize maximizable: false, // prevents maximize
@ -41,6 +42,10 @@ app.on('ready', async () => {
} }
// pollResources(mainWindow); // pollResources(mainWindow);
// 🔹 Register all IPC handlers (includes polling)
registerIPCHandlers(mainWindow);
pollResources(mainWindow);
// IPC handlers // IPC handlers
// ipcMainHandle('getStaticData', () => getStaticData()); // ipcMainHandle('getStaticData', () => getStaticData());

View File

@ -10,7 +10,8 @@ contextBridge.exposeInMainWorld('electron', {
getActiveAppDetails: () => ipcRenderer.invoke('get-active-app'), getActiveAppDetails: () => ipcRenderer.invoke('get-active-app'),
toggleCloseBtn: (shouldEnable: boolean) => ipcRenderer.invoke('toggle-close-btn', shouldEnable), toggleCloseBtn: (shouldEnable: boolean) => ipcRenderer.invoke('toggle-close-btn', shouldEnable),
resizeWindow: (width: number, height: number) => ipcRenderer.invoke('resize-window', width, height), resizeWindow: (width: number, height: number) => ipcRenderer.invoke('resize-window', width, height),
getRunningApps: () => ipcRenderer.invoke('get-running-apps'), onWindowUpdate: (callback) =>
ipcRenderer.on("window:updated", (_, data) => callback(data)),
} satisfies Window['electron']); } satisfies Window['electron']);
// function ipcInvoke<Key extends keyof EventPayloadMapping>( // function ipcInvoke<Key extends keyof EventPayloadMapping>(

View File

@ -1,4 +1,4 @@
import { desktopCapturer } from "electron"; import { BrowserWindow, desktopCapturer } from "electron";
import * as fs from "fs"; import * as fs from "fs";
import path from "path"; import path from "path";
import axios from 'axios'; import axios from 'axios';
@ -95,7 +95,23 @@ export const handleLogin = async (credentials: { email: string, password: string
} }
} }
// Get active window info with timestamp
export const getActiveApp = async () => { export const getActiveApp = async () => {
const result = await activeWindow(); const result = await activeWindow();
return result; const dataWithTime = { ...result, timestamp: new Date().toISOString() };
// console.log("🪟 Active Window with Time:", dataWithTime);
return dataWithTime;
};
// Periodically send active window updates
export const startWindowPolling = (win: BrowserWindow) => {
console.log("⏱️ Started active window polling (every 5s)");
setInterval(async () => {
try {
const app = await getActiveApp();
if (app) win.webContents.send("window:updated", app);
} catch (err) {
console.error("❌ Polling error:", err);
}
}, 5000);
}; };

View File

@ -0,0 +1,51 @@
// src/hooks/useActiveAppTracker.ts
import { useEffect, useRef } from "react";
import { useDispatch } from "react-redux";
import { setActiveWindow, WindowInfo } from "../store/windowSlice";
export const useActiveAppTracker = (
isTracking: boolean,
projectId: string,
projectName: string
) => {
const dispatch = useDispatch();
const lastActiveRef = useRef<WindowInfo | null>(null);
useEffect(() => {
if (!isTracking || !projectId) {
console.log(" Tracking not active or project not selected");
return;
}
console.log(` Tracking started for project: ${projectName}`);
const fetchActiveApp = async () => {
try {
const details = (await window.electron.getActiveAppDetails()) as WindowInfo;
if (!details || !details.id) return;
const prev = lastActiveRef.current;
const hasChanged =
!prev ||
prev.title !== details.title ||
prev.owner.processId !== details.owner.processId;
if (hasChanged) {
console.log("🪟 Window changed:", details.title);
dispatch(setActiveWindow({ window: details, projectId, projectName }));
lastActiveRef.current = details;
}
} catch (error) {
console.error("❌ Failed to fetch active app:", error);
}
};
fetchActiveApp();
const intervalId = setInterval(fetchActiveApp, 5000);
return () => {
console.log("🛑 Stopping active window tracker");
clearInterval(intervalId);
};
}, [isTracking, dispatch, projectId, projectName]);
};

View File

@ -22,7 +22,11 @@ import {
ShieldBan, ShieldBan,
ChevronDown, ChevronDown,
Play, Play,
ChevronUp,
} from "lucide-react"; } from "lucide-react";
import { useActiveAppTracker } from "../hooks/useActiveAppTracker";
import { useSelector } from "react-redux";
import { RootState } from "../store";
interface User { interface User {
email: string; email: string;
@ -43,7 +47,7 @@ interface ProjectTrackingData {
isPaused: boolean; isPaused: boolean;
pauseStartTime: Date | null; pauseStartTime: Date | null;
totalPausedTime: number; totalPausedTime: number;
accumulatedTime: number; // ✅ NEW: Stores time from previous sessions accumulatedTime: number; // NEW: Stores time from previous sessions
} }
interface TrackingState { interface TrackingState {
@ -67,11 +71,17 @@ interface DashboardLayoutProps {
} }
const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => { const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
const [isSidebarOpen, setSidebarOpen] = useState(true); const [isSidebarOpen, setSidebarOpen] = useState(true);
const [isMobile, setIsMobile] = useState(window.innerWidth < 768); const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const { windowLogs } = useSelector((state: RootState) => state.window);
console.log("🧾 windowLogs in component:", windowLogs);
// Tracking states // Tracking states
const [selectedProject, setSelectedProject] = useState<Project | null>(null); const [selectedProject, setSelectedProject] = useState<Project | null>(null);
const [isTracking, setIsTracking] = useState(false); const [isTracking, setIsTracking] = useState(false);
@ -81,6 +91,8 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
const [endTime, setEndTime] = useState<Date | null>(null); const [endTime, setEndTime] = useState<Date | null>(null);
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null); const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
useActiveAppTracker(isTracking, selectedProject?.id, selectedProject?.projectName);
const [projectsData, setProjectsData] = useState<Project[]>([ const [projectsData, setProjectsData] = useState<Project[]>([
{ id: 1, projectName: "CRM Redesign", clientName: "Salesforce" }, { id: 1, projectName: "CRM Redesign", clientName: "Salesforce" },
{ id: 2, projectName: "AI Assistant", clientName: "OpenAI" }, { id: 2, projectName: "AI Assistant", clientName: "OpenAI" },
@ -105,21 +117,6 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
}; };
useEffect(() => {
setInterval(async () => {
const details = await window.electron.getActiveAppDetails()
console.log(details)
}, 15000)
}, [])
const toggleSidebar = () => setSidebarOpen(!isSidebarOpen);
const closeSidebarOnMobile = () => {
if (isMobile) setSidebarOpen(false);
};
const menuItems = [ const menuItems = [
{ label: "Dashboard", path: "/dashboard", icon: <Home size={18} />, exact: true }, { label: "Dashboard", path: "/dashboard", icon: <Home size={18} />, exact: true },
{ label: "Projects", path: "/dashboard/projects", icon: <FolderKanban size={18} /> }, { label: "Projects", path: "/dashboard/projects", icon: <FolderKanban size={18} /> },
@ -129,7 +126,7 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
{ label: "Help & Support", path: "/dashboard/help-support", icon: <HelpCircleIcon size={18} /> }, { label: "Help & Support", path: "/dashboard/help-support", icon: <HelpCircleIcon size={18} /> },
]; ];
// ✅ Sync global states with selected project tracking data // Sync global states with selected project tracking data
useEffect(() => { useEffect(() => {
if (selectedProject) { if (selectedProject) {
const projectData = projectTrackingMap.get(selectedProject.id); const projectData = projectTrackingMap.get(selectedProject.id);
@ -143,7 +140,7 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
} }
}, [selectedProject, projectTrackingMap]); }, [selectedProject, projectTrackingMap]);
// ✅ Timer effect - calculates elapsed time based on timestamps + accumulated time // Timer effect - calculates elapsed time based on timestamps + accumulated time
useEffect(() => { useEffect(() => {
if (isTracking && !isPaused && selectedProject) { if (isTracking && !isPaused && selectedProject) {
timerRef.current = setInterval(() => { timerRef.current = setInterval(() => {
@ -157,7 +154,7 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
const currentSessionTime = Math.floor((now - startTimestamp) / 1000); const currentSessionTime = Math.floor((now - startTimestamp) / 1000);
const totalPausedTime = current.totalPausedTime || 0; const totalPausedTime = current.totalPausedTime || 0;
// ✅ FIX: Add accumulated time from previous sessions // FIX: Add accumulated time from previous sessions
const calculatedElapsed = const calculatedElapsed =
(current.accumulatedTime || 0) + currentSessionTime - totalPausedTime; (current.accumulatedTime || 0) + currentSessionTime - totalPausedTime;
@ -185,7 +182,7 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
}; };
}, [isTracking, isPaused, selectedProject]); }, [isTracking, isPaused, selectedProject]);
// ✅ Start tracking // Start tracking
const handleStartTracking = (project: Project) => { const handleStartTracking = (project: Project) => {
if (timerRef.current) { if (timerRef.current) {
clearInterval(timerRef.current); clearInterval(timerRef.current);
@ -193,7 +190,7 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
} }
const now = new Date(); const now = new Date();
window.electron.toggleCloseBtn(false)
setProjectTrackingMap((prev) => { setProjectTrackingMap((prev) => {
const updated = new Map(prev); const updated = new Map(prev);
const current = updated.get(project.id) || { const current = updated.get(project.id) || {
@ -207,7 +204,7 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
accumulatedTime: 0, accumulatedTime: 0,
}; };
// ✅ Keep accumulated time from previous sessions // Keep accumulated time from previous sessions
current.startTime = now; current.startTime = now;
current.isTracking = true; current.isTracking = true;
current.isPaused = false; current.isPaused = false;
@ -225,7 +222,7 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
}); });
}; };
// ✅ Pause/Resume tracking with pause duration tracking // Pause/Resume tracking with pause duration tracking
const handlePauseResume = () => { const handlePauseResume = () => {
if (!selectedProject) return; if (!selectedProject) return;
@ -263,7 +260,7 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
}); });
}; };
// ✅ Stop tracking - PRESERVES elapsed time // Stop tracking - PRESERVES elapsed time
const handleStopTracking = () => { const handleStopTracking = () => {
if (!selectedProject) return; if (!selectedProject) return;
@ -273,13 +270,13 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
} }
const now = new Date(); const now = new Date();
window.electron.toggleCloseBtn(true)
setProjectTrackingMap((prev) => { setProjectTrackingMap((prev) => {
const updated = new Map(prev); const updated = new Map(prev);
const current = updated.get(selectedProject.id); const current = updated.get(selectedProject.id);
if (!current) return updated; if (!current) return updated;
// ✅ FIX: Store current elapsed time as accumulated time // FIX: Store current elapsed time as accumulated time
current.accumulatedTime = current.elapsedTime; current.accumulatedTime = current.elapsedTime;
current.isTracking = false; current.isTracking = false;
current.isPaused = false; current.isPaused = false;
@ -291,7 +288,7 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
setIsTracking(false); setIsTracking(false);
setIsPaused(false); setIsPaused(false);
setEndTime(now); setEndTime(now);
setElapsedTime(current.elapsedTime); // ✅ Keep the elapsed time setElapsedTime(current.elapsedTime); // Keep the elapsed time
return updated; return updated;
}); });
@ -308,7 +305,7 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
}; };
}, []); }, []);
// ✅ Handle project selection // Handle project selection
const handleProjectSelect = (id: number) => { const handleProjectSelect = (id: number) => {
if (isTracking) return; if (isTracking) return;
@ -355,6 +352,8 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
return `${hrs}h ${mins}m ${secs}s`; return `${hrs}h ${mins}m ${secs}s`;
}; };
const toggleWindowSize = useRef(true); // true -> Open, false -> Close
return ( return (
<> <>
{/* Header */} {/* Header */}
@ -413,22 +412,20 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
</span> </span>
{/* Dropdown */} {/* Dropdown */}
<div className="dropdown dropdown-end"> <button
<div onClick={() => {
tabIndex={0} if (toggleWindowSize.current) {
role="button"
className="btn btn-sm btn-outline btn-primary p-1.5 rounded-lg" window.electron.resizeWindow(500, 85)
> toggleWindowSize.current = false;
<ChevronDown /> } else {
</div> window.electron.resizeWindow(500, 600)
<ul toggleWindowSize.current = true;
tabIndex={0} }
className="dropdown-content menu p-2 shadow-lg bg-base-100 rounded-box w-40 mt-2" }}
> className="btn btn-xs btn-outline btn-primary ">
<li><a>View Logs</a></li> {toggleWindowSize.current ? <ChevronDown /> : <ChevronUp />}
<li><a>Settings</a></li> </button>
</ul>
</div>
</div> </div>
</header> </header>
@ -445,8 +442,7 @@ const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
to={item.path} to={item.path}
end={item.exact} end={item.exact}
className={({ isActive }) => className={({ isActive }) =>
`flex items-center justify-center w-10 h-10 rounded-lg transition-all duration-150 cursor-pointer ${ `flex items-center justify-center w-10 h-10 rounded-lg transition-all duration-150 cursor-pointer ${isActive
isActive
? "bg-primary text-primary-content" ? "bg-primary text-primary-content"
: "hover:bg-base-300 text-base-content" : "hover:bg-base-300 text-base-content"
}` }`

View File

@ -2,9 +2,11 @@ import React from 'react'
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import App from './App' import App from './App'
import './index.css' import './index.css'
import { Provider } from "react-redux";
import { store } from "./store";
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode> <Provider store={store}>
<App /> <App />
</React.StrictMode>, </Provider>
) )

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { useRef } from "react";
import { Clock, TrendingUp, Play, Square, ProjectorIcon, Pause } from "lucide-react"; import { Clock, TrendingUp, Play, Square, ProjectorIcon, Pause } from "lucide-react";
import { useOutletContext } from "react-router-dom"; import { useOutletContext } from "react-router-dom";
@ -91,6 +91,9 @@ const Dashboard: React.FC = () => {
? projectTrackingMap.get(selectedProject.id) ? projectTrackingMap.get(selectedProject.id)
: null; : null;
return ( return (
<main className="flex-1 overflow-y-auto no-scrollbar"> <main className="flex-1 overflow-y-auto no-scrollbar">
{/* Header */} {/* Header */}
@ -104,35 +107,6 @@ const Dashboard: React.FC = () => {
> >
Screenshot Test Screenshot Test
</button> </button>
<button
onClick={() => {
window.electron.toggleCloseBtn(true)
}}
className="btn btn-xs btn-outline mt-2">
Enable
</button>
<button
onClick={() => {
window.electron.toggleCloseBtn(false)
}}
className="btn btn-xs btn-outline mt-2">
Disable
</button>
<button
onClick={() => {
window.electron.resizeWindow(500, 100)
}}
className="btn btn-xs btn-outline mt-2">
Resize Toggle
</button>
<button
onClick={async () => {
const res = await window.electron.getRunningApps()
console.log(res, "Line 131")
}}
className="btn btn-xs btn-outline mt-2">
Get Running Apps
</button>
</div> </div>
{/* Widgets */} {/* Widgets */}

View File

@ -1,5 +1,7 @@
import React from "react"; import React from "react";
import { CalendarDays, Clock, FileText, CheckCircle, XCircle } from "lucide-react"; import { CalendarDays, Clock, FileText, CheckCircle, XCircle } from "lucide-react";
import { useSelector } from "react-redux";
import { RootState } from "../../store";
interface TimesheetEntry { interface TimesheetEntry {
id: number; id: number;
@ -22,6 +24,10 @@ const Timesheets: React.FC = () => {
const totalHours = timesheetData.reduce((acc, t) => acc + t.hours, 0); const totalHours = timesheetData.reduce((acc, t) => acc + t.hours, 0);
const approvedCount = timesheetData.filter((t) => t.status === "Approved").length; const approvedCount = timesheetData.filter((t) => t.status === "Approved").length;
const { windowLogs } = useSelector((state: RootState) => state.window);
console.log("🧾 Timesheet in component:", windowLogs);
return ( return (
<div className="flex flex-col gap-2 max-h-screen"> <div className="flex flex-col gap-2 max-h-screen">
{/* Header */} {/* Header */}

12
src/ui/store/index.tsx Normal file
View File

@ -0,0 +1,12 @@
// src/store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import windowReducer from "./windowSlice";
export const store = configureStore({
reducer: {
window: windowReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

View File

@ -0,0 +1,128 @@
// src/store/windowSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
export interface OwnerInfo {
processId: number;
path: string;
name: string;
}
export interface WindowInfo {
platform: string;
id: number;
title: string;
owner: OwnerInfo;
bounds: Record<string, number>;
contentBounds: Record<string, number>;
memoryUsage: number;
timestamp: string;
}
export interface ProjectWindowLog {
projectId: string;
projectName: string;
appName: string;
windowTitle: string;
platform: string;
duration: number; // in seconds
startTime: string;
endTime: string;
memoryUsage: number;
}
interface WindowState {
activeWindow: WindowInfo | null;
activeSince: number | null;
activeProjectId: string | null;
activeProjectName: string | null;
windowLogs: ProjectWindowLog[];
}
const initialState: WindowState = {
activeWindow: null,
activeSince: null,
activeProjectId: null,
activeProjectName: null,
windowLogs: [],
};
interface SetActiveWindowPayload {
window: WindowInfo;
projectId: string;
projectName: string;
}
const windowSlice = createSlice({
name: "window",
initialState,
reducers: {
// ✅ Start tracking window for a project
setActiveWindow: (state, action: PayloadAction<SetActiveWindowPayload>) => {
const { window, projectId, projectName } = action.payload;
const now = Date.now();
// 🕒 Log previous window before switching
if (state.activeWindow && state.activeSince && state.activeProjectId) {
const duration = Math.floor((now - state.activeSince) / 1000);
const logEntry: ProjectWindowLog = {
projectId: state.activeProjectId,
projectName: state.activeProjectName || "Untitled",
appName: state.activeWindow.owner.name,
windowTitle: state.activeWindow.title,
platform: state.activeWindow.platform,
duration,
startTime: new Date(state.activeSince).toISOString(),
endTime: new Date(now).toISOString(),
memoryUsage: state.activeWindow.memoryUsage,
};
state.windowLogs.push(logEntry);
console.log("🧾 Log saved:", logEntry);
}
// 🔁 Update new active window + project context
state.activeWindow = window;
state.activeSince = now;
state.activeProjectId = projectId;
state.activeProjectName = projectName;
},
// ✅ Stop tracking (finalize)
finalizeActiveWindow: (state) => {
if (state.activeWindow && state.activeSince && state.activeProjectId) {
const now = Date.now();
const duration = Math.floor((now - state.activeSince) / 1000);
const logEntry: ProjectWindowLog = {
projectId: state.activeProjectId,
projectName: state.activeProjectName || "Untitled",
appName: state.activeWindow.owner.name,
windowTitle: state.activeWindow.title,
platform: state.activeWindow.platform,
duration,
startTime: new Date(state.activeSince).toISOString(),
endTime: new Date(now).toISOString(),
memoryUsage: state.activeWindow.memoryUsage,
};
state.windowLogs.push(logEntry);
console.log("✅ Final log saved:", logEntry);
}
state.activeWindow = null;
state.activeSince = null;
state.activeProjectId = null;
state.activeProjectName = null;
},
clearWindowLogs: (state) => {
state.windowLogs = [];
},
},
});
export const { setActiveWindow, finalizeActiveWindow, clearWindowLogs } =
windowSlice.actions;
export default windowSlice.reducer;

2
types.d.ts vendored
View File

@ -32,7 +32,7 @@ interface Window {
getActiveAppDetails: () => {}; getActiveAppDetails: () => {};
toggleCloseBtn: (shouldEnable: boolean) => {}, toggleCloseBtn: (shouldEnable: boolean) => {},
resizeWindow: (width: number, height: number) => {}, resizeWindow: (width: number, height: number) => {},
getRunningApps: () => Promise<{name: string, pid:string}[]>, onWindowUpdate: (callback: (data: any) => void) => void;
}; };
} }