diff --git a/package-lock.json b/package-lock.json index 1bb8d85..0808058 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "chart.js": "^4.4.1", "cors": "^2.8.5", "net": "^1.0.2", - "next": "^14.2.18", + "next": "14.0.3", "nextjs-cors": "^2.2.0", "react": "^18", "react-chartjs-2": "^5.2.0", @@ -52,79 +52,55 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", - "license": "MIT", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", + "integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==", "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "license": "MIT", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "license": "MIT", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "license": "MIT", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", - "license": "MIT", + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dependencies": { - "@babel/types": "^7.26.0" - }, - "bin": { - "parser": "bin/babel-parser.js" + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=6.9.0" } }, "node_modules/@babel/runtime": { @@ -138,46 +114,14 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", - "license": "MIT", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.4.tgz", + "integrity": "sha512-7uIFwVYpoplT5jp/kVv6EF93VaJ8H+Yn5IczYiaAi98ajzjfoZfslet/e0sLh+wVBjb2qqIut1b0S26VSafsSQ==", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" }, "engines": { "node": ">=6.9.0" @@ -1881,48 +1825,48 @@ "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "license": "MIT", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", + "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" + "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "license": "MIT" + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "license": "MIT", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1945,19 +1889,17 @@ } }, "node_modules/@next/env": { - "version": "14.2.18", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.18.tgz", - "integrity": "sha512-2vWLOUwIPgoqMJKG6dt35fVXVhgM09tw4tK3/Q34GFXDrfiHlG7iS33VA4ggnjWxjiz9KV5xzfsQzJX6vGAekA==", - "license": "MIT" + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.3.tgz", + "integrity": "sha512-7xRqh9nMvP5xrW4/+L0jgRRX+HoNRGnfJpD+5Wq6/13j3dsdzxO3BCXn7D3hMqsDb+vjZnJq+vI7+EtgrYZTeA==" }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.18", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.18.tgz", - "integrity": "sha512-tOBlDHCjGdyLf0ube/rDUs6VtwNOajaWV+5FV/ajPgrvHeisllEdymY/oDgv2cx561+gJksfMUtqf8crug7sbA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.3.tgz", + "integrity": "sha512-64JbSvi3nbbcEtyitNn2LEDS/hcleAFpHdykpcnrstITFlzFgB/bW0ER5/SJJwUPj+ZPY+z3e+1jAfcczRLVGw==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -1967,13 +1909,12 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.18", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.18.tgz", - "integrity": "sha512-uJCEjutt5VeJ30jjrHV1VIHCsbMYnEqytQgvREx+DjURd/fmKy15NaVK4aR/u98S1LGTnjq35lRTnRyygglxoA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.3.tgz", + "integrity": "sha512-RkTf+KbAD0SgYdVn1XzqE/+sIxYGB7NLMZRn9I4Z24afrhUpVJx6L8hsRnIwxz3ERE2NFURNliPjJ2QNfnWicQ==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -1983,13 +1924,12 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.18", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.18.tgz", - "integrity": "sha512-IL6rU8vnBB+BAm6YSWZewc+qvdL1EaA+VhLQ6tlUc0xp+kkdxQrVqAnh8Zek1ccKHlTDFRyAft0e60gteYmQ4A==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.3.tgz", + "integrity": "sha512-3tBWGgz7M9RKLO6sPWC6c4pAw4geujSwQ7q7Si4d6bo0l6cLs4tmO+lnSwFp1Tm3lxwfMk0SgkJT7EdwYSJvcg==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -1999,13 +1939,12 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.18", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.18.tgz", - "integrity": "sha512-RCaENbIZqKKqTlL8KNd+AZV/yAdCsovblOpYFp0OJ7ZxgLNbV5w23CUU1G5On+0fgafrsGcW+GdMKdFjaRwyYA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.3.tgz", + "integrity": "sha512-v0v8Kb8j8T23jvVUWZeA2D8+izWspeyeDGNaT2/mTHWp7+37fiNfL8bmBWiOmeumXkacM/AB0XOUQvEbncSnHA==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2015,13 +1954,12 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.18", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.18.tgz", - "integrity": "sha512-3kmv8DlyhPRCEBM1Vavn8NjyXtMeQ49ID0Olr/Sut7pgzaQTo4h01S7Z8YNE0VtbowyuAL26ibcz0ka6xCTH5g==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.3.tgz", + "integrity": "sha512-VM1aE1tJKLBwMGtyBR21yy+STfl0MapMQnNrXkxeyLs0GFv/kZqXS5Jw/TQ3TSUnbv0QPDf/X8sDXuMtSgG6eg==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2031,13 +1969,12 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.18", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.18.tgz", - "integrity": "sha512-mliTfa8seVSpTbVEcKEXGjC18+TDII8ykW4a36au97spm9XMPqQTpdGPNBJ9RySSFw9/hLuaCMByluQIAnkzlw==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.3.tgz", + "integrity": "sha512-64EnmKy18MYFL5CzLaSuUn561hbO1Gk16jM/KHznYP3iCIfF9e3yULtHaMy0D8zbHfxset9LTOv6cuYKJgcOxg==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -2047,13 +1984,12 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.18", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.18.tgz", - "integrity": "sha512-J5g0UFPbAjKYmqS3Cy7l2fetFmWMY9Oao32eUsBPYohts26BdrMUyfCJnZFQkX9npYaHNDOWqZ6uV9hSDPw9NA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.3.tgz", + "integrity": "sha512-WRDp8QrmsL1bbGtsh5GqQ/KWulmrnMBgbnb+59qNTW1kVi1nG/2ndZLkcbs2GX7NpFLlToLRMWSQXmPzQm4tog==", "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -2063,13 +1999,12 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.18", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.18.tgz", - "integrity": "sha512-Ynxuk4ZgIpdcN7d16ivJdjsDG1+3hTvK24Pp8DiDmIa2+A4CfhJSEHHVndCHok6rnLUzAZD+/UOKESQgTsAZGg==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.3.tgz", + "integrity": "sha512-EKffQeqCrj+t6qFFhIFTRoqb2QwX1mU7iTOvMyLbYw3QtqTw9sMwjykyiMlZlrfm2a4fA84+/aeW+PMg1MjuTg==", "cpu": [ "ia32" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -2079,13 +2014,12 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.18", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.18.tgz", - "integrity": "sha512-dtRGMhiU9TN5nyhwzce+7c/4CCeykYS+ipY/4mIrGzJ71+7zNo55ZxCB7cAVuNqdwtYniFNR2c9OFQ6UdFIMcg==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.3.tgz", + "integrity": "sha512-ERhKPSJ1vQrPiwrs15Pjz/rvDHZmkmvbf/BjPN/UCOI++ODftT0GtasDPi0j+y6PPJi5HsXw+dpRaXUaw4vjuQ==", "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -2280,19 +2214,11 @@ "size-limit": "11.0.0" } }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "license": "Apache-2.0" - }, "node_modules/@swc/helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", - "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", - "license": "Apache-2.0", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", + "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", "dependencies": { - "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, @@ -2394,6 +2320,17 @@ "node": ">=8" } }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -2557,9 +2494,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, "funding": [ { @@ -2575,12 +2512,11 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.1" + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -2648,9 +2584,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001680", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz", - "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==", + "version": "1.0.30001641", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001641.tgz", + "integrity": "sha512-Phv5thgl67bHYo1TtMY/MurjkHhV4EDaCosezRXgZ8jzA/Ub+wjxAvbGvjoFENStinwi5kCyOYV3mi5tOGykwA==", "funding": [ { "type": "opencollective", @@ -2685,6 +2621,27 @@ "react": ">=17.0.0" } }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/chart.js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", @@ -2759,6 +2716,19 @@ "node": ">=0.10.0" } }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, "node_modules/color2k": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.2.tgz", @@ -2852,10 +2822,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "license": "MIT", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2918,23 +2887,6 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, - "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/default-browser": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", @@ -3013,11 +2965,10 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.62", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.62.tgz", - "integrity": "sha512-t8c+zLmJHa9dJy96yBZRXGQYoiCEnHYgFwn1asvSPZSUdVxnB62A4RASd7k41ytG3ErFBA0TpHlKg9D9SQBmLg==", - "dev": true, - "license": "ISC" + "version": "1.4.594", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.594.tgz", + "integrity": "sha512-xT1HVAu5xFn7bDfkjGQi9dNpMqGchUkebwf1GL7cZN32NSwwlHRPMSDJ1KN6HkS0bWUtndbSQZqvpQftKG2uFQ==", + "dev": true }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -3122,10 +3073,9 @@ } }, "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "engines": { "node": ">=6" } @@ -3420,14 +3370,10 @@ "node": ">=10.13.0" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "license": "MIT", - "engines": { - "node": ">=4" - } + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "node_modules/globby": { "version": "14.0.0", @@ -3462,8 +3408,7 @@ "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" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/graphql": { "version": "15.8.0", @@ -3486,6 +3431,14 @@ "graphql": "14 - 16" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, "node_modules/hasown": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", @@ -3729,18 +3682,6 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, - "node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -3857,12 +3798,6 @@ "node": "*" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -3905,18 +3840,17 @@ "integrity": "sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ==" }, "node_modules/next": { - "version": "14.2.18", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.18.tgz", - "integrity": "sha512-H9qbjDuGivUDEnK6wa+p2XKO+iMzgVgyr9Zp/4Iv29lKa+DYaxJGjOeEA+5VOvJh/M7HLiskehInSa0cWxVXUw==", - "license": "MIT", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/next/-/next-14.0.3.tgz", + "integrity": "sha512-AbYdRNfImBr3XGtvnwOxq8ekVCwbFTv/UJoLwmaX89nk9i051AEY4/HAWzU0YpaTDw8IofUpmuIlvzWF13jxIw==", "dependencies": { - "@next/env": "14.2.18", - "@swc/helpers": "0.5.5", + "@next/env": "14.0.3", + "@swc/helpers": "0.5.2", "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001579", - "graceful-fs": "^4.2.11", + "caniuse-lite": "^1.0.30001406", "postcss": "8.4.31", - "styled-jsx": "5.1.1" + "styled-jsx": "5.1.1", + "watchpack": "2.4.0" }, "bin": { "next": "dist/bin/next" @@ -3925,19 +3859,18 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.18", - "@next/swc-darwin-x64": "14.2.18", - "@next/swc-linux-arm64-gnu": "14.2.18", - "@next/swc-linux-arm64-musl": "14.2.18", - "@next/swc-linux-x64-gnu": "14.2.18", - "@next/swc-linux-x64-musl": "14.2.18", - "@next/swc-win32-arm64-msvc": "14.2.18", - "@next/swc-win32-ia32-msvc": "14.2.18", - "@next/swc-win32-x64-msvc": "14.2.18" + "@next/swc-darwin-arm64": "14.0.3", + "@next/swc-darwin-x64": "14.0.3", + "@next/swc-linux-arm64-gnu": "14.0.3", + "@next/swc-linux-arm64-musl": "14.0.3", + "@next/swc-linux-x64-gnu": "14.0.3", + "@next/swc-linux-x64-musl": "14.0.3", + "@next/swc-win32-arm64-msvc": "14.0.3", + "@next/swc-win32-ia32-msvc": "14.0.3", + "@next/swc-win32-x64-msvc": "14.0.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" @@ -3946,9 +3879,6 @@ "@opentelemetry/api": { "optional": true }, - "@playwright/test": { - "optional": true - }, "sass": { "optional": true } @@ -3985,11 +3915,10 @@ } }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true, - "license": "MIT" + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true }, "node_modules/normalize-path": { "version": "3.0.0", @@ -4168,10 +4097,9 @@ } }, "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -4987,6 +4915,17 @@ "node": ">=8" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -5082,6 +5021,14 @@ "resolved": "https://registry.npmjs.org/tls/-/tls-0.0.1.tgz", "integrity": "sha512-GzHpG+hwupY8VMR6rYsnAhTHqT/97zT45PG8WD5eTT1lq+dFE0nN+1PYpsoBcHJgSmTz5ceK2Cv88IkPmIPOtQ==" }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5144,9 +5091,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "funding": [ { @@ -5162,10 +5109,9 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.0" + "escalade": "^3.1.1", + "picocolors": "^1.0.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -5242,6 +5188,18 @@ "node": ">= 0.8" } }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index faf5fa3..16a7cd5 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "chart.js": "^4.4.1", "cors": "^2.8.5", "net": "^1.0.2", - "next": "^14.2.18", + "next": "14.0.3", "nextjs-cors": "^2.2.0", "react": "^18", "react-chartjs-2": "^5.2.0", diff --git a/server/harmonize.py b/server/harmonize.py index be8e675..8c4b0de 100644 --- a/server/harmonize.py +++ b/server/harmonize.py @@ -132,14 +132,10 @@ class_data["sched"] = list(set(class_data["sched"])) if "description" not in class_data: class_data["description"] = instances[0]["description"] - class_data["credits"] = next( - (instance["credits"] for instance in instances if " 0) { const paramsGetTeacher = new URLSearchParams({ id: profs[0].id }); - const responseRMP = await fetch(baseUrl + "/api/ratings/getTeacher?" + paramsGetTeacher); + const responseRMP = await fetch("/api/ratings/getTeacher?" + paramsGetTeacher); const RMPrating = await responseRMP.json(); rating = RMPrating.RMPrating.avgRating; break; @@ -75,18 +68,11 @@ async function getAllRMPRatings(allProfs) { } -// Add these new utility functions before loadRatingsForProfs -export function getRMPScore(rmpData, instructor) { - if (!rmpData || !instructor) return null; - return rmpData[instructor] || null; -} - - // Asynchronously fetch RMP rating for all professors let isLoadingRatings = false; -export async function loadRatingsForProfs(course, onUpdate = null) { - if (!course) return Promise.resolve({}); +export async function loadRatingsForProfs(course) { + if (!course) return Promise.resolve({}); // Return an empty object if no course const allProfs = []; for (const semester in course.instructor) { @@ -97,21 +83,16 @@ export async function loadRatingsForProfs(course, onUpdate = null) { } } - if (isLoadingRatings) return Promise.resolve({}); + if (isLoadingRatings) { + return Promise.resolve({}); // Return an empty object if already loading + } + isLoadingRatings = true; - const ratings = {}; try { - // Process professors individually for streaming updates - const promises = allProfs.map(async (instructor) => { - const rating = await getRMPRating(instructor); - ratings[instructor] = rating; - // Call callback with accumulated ratings so far - if (onUpdate) onUpdate({...ratings}); - }); - - await Promise.all(promises); - return ratings; + const ratings = await getAllRMPRatings(allProfs); + const newRMP = { ...ratings }; + return newRMP; } catch (error) { console.error("Error loading ratings:", error); return Promise.resolve({}); diff --git a/src/components/calendar.js b/src/components/calendar.js index 4377d2a..eaf90d6 100644 --- a/src/components/calendar.js +++ b/src/components/calendar.js @@ -13,27 +13,118 @@ import { Spinner, } from '@chakra-ui/react' -import { convertNumberToTime } from './schedule'; -import { calculateEndTime } from './scheduleManager'; -// Component const Calendar = (props) => { + const { subjectCode, courseCode, title } = props; - const [courseData, setCourseData] = useState(null); + const [lectures, setLectures] = useState({ + Monday: [], + Tuesday: [], + Wednesday: [], + Thursday: [], + Friday: [] + }); const [wait, setWait] = useState(true); - const [hoveredCrn, setHoveredCrn] = useState(null); // Add this line - useEffect(() => { - const fetchCourseData = async () => { - const data = await getCourseData(subjectCode, courseCode, title); - setCourseData(data); + + function convertTo12HourFormat(time) { + const [hour, minute] = time.split(':'); + + const period = hour >= 12 ? 'PM' : 'AM'; + + const hour12 = hour % 12 || 12; + + const time12 = `${hour12}:${minute} ${period}`; + + return time12; + } + + + // Get purdue.io data for course sections and lecture times + const getCourseData = async (subjectCode, courseCode, title) => { + + const updatedLectures = { + Monday: [], + Tuesday: [], + Wednesday: [], + Thursday: [], + Friday: [] + }; + + let data; + + try { + const semester = "202520"; + const url = "https://api.purdue.io/odata/Courses?$expand=Classes($filter=Term/Code eq '" + semester + "';$expand=Sections($expand=Meetings($expand=Instructors)))&$filter=Subject/Abbreviation eq '" + subjectCode + "' and Number eq '" + courseCode + "' and Title eq '" + title + "'"; + // console.log(url); + const response = await fetch(url); + setWait(false); + data = await response.json(); + + setLectures(updatedLectures); + + data = data.value[0]; + + } catch (e) { + setWait(false); + return; + } + + if (!data) { + setWait(false); + return; + } + + for (const cls of data.Classes) { + for (const section of cls.Sections) { + for (const meeting of section.Meetings) { + try { + const { DaysOfWeek, StartDate, EndDate, Type } = meeting; + const startTimeRaw = meeting.StartTime; + const startTime = convertTo12HourFormat(startTimeRaw); + const duration = meeting.Duration.split("PT")[1]; + const instructors = meeting.Instructors.map(instr => instr.Name); + + const lecture = { + startDate: StartDate, + endDate: EndDate, + type: Type, + startTime, + startTimeRaw, + duration, + instructors + }; + + DaysOfWeek.split(",").forEach(day => { + updatedLectures[day.trim()].push(lecture); + }); + } catch (e) { + continue; + } + + } + } + } + + for (const day in updatedLectures) { + updatedLectures[day].sort((a, b) => { + const aTime = a.startTimeRaw.split(':'); + const bTime = b.startTimeRaw.split(':'); + const aDate = new Date(1970, 0, 1, aTime[0], aTime[1], 0); + const bDate = new Date(1970, 0, 1, bTime[0], bTime[1], 0); + return aDate - bDate; + }); } + // console.log(updatedLectures); + + setLectures(updatedLectures); + } - fetchCourseData(); - setWait(false); + useEffect(() => { + getCourseData(subjectCode, courseCode, title); }, [subjectCode, courseCode, title]); - if (!courseData?.Classes?.[0]?.Sections) { + if (Object.values(lectures).every(lecture => lecture.length === 0)) { return ( <>
Spring 2025 Schedule:
@@ -44,142 +135,108 @@ const Calendar = (props) => { ) } - // Helper function to get meetings for a specific day - const getMeetingsForDay = (day) => { - const meetings = []; - courseData.Classes.forEach(course => { - course.Sections.forEach(section => { - section.Meetings.forEach(meeting => { - if (meeting.DaysOfWeek.includes(day)) { - const startTime = meeting.StartTime.split('.')[0]; - const duration = meeting.Duration.replace('PT', ''); - const end = calculateEndTime(startTime, duration); - const endTime = convertNumberToTime(end); - - meetings.push({ - ...meeting, - sectionType: section.Type, - crn: section.Crn, - room: `${meeting.Room.Building.ShortCode} ${meeting.Room.Number}`, - startTime: convertTo12HourFormat(meeting.StartTime.split('.')[0]), - endTime, - days: meeting.DaysOfWeek, - detailId: props.detailId // Add this line - }); - } - }); - }); - }); - - return meetings.sort((a, b) => { - const aTime = a.StartTime.split(':'); - const bTime = b.StartTime.split(':'); - return (parseInt(aTime[0]) * 60 + parseInt(aTime[1])) - - (parseInt(bTime[0]) * 60 + parseInt(bTime[1])); - }); - }; - - - const days = ['M', 'T', 'W', 'R', 'F']; return ( <>
Spring 2025 Schedule:
{/* Calendar View for Lecture Times */}
- {days.map((day, index) => ( -
-

{day.charAt(0)}

-
- {getMeetingsForDay(day).map((meeting, i) => ( - - ))} -
+
+

M

+
+ {lectures.Monday.map((lecture, i) => { + return ( + + ) + })}
- ))} +
+
+

T

+
+ {lectures.Tuesday.map((lecture, i) => { + return ( + + ) + })} +
+
+
+

W

+
+ {lectures.Wednesday.map((lecture, i) => { + return ( + + ) + })} +
+
+
+

T

+
+ {lectures.Thursday.map((lecture, i) => { + return ( + + ) + })} +
+
+
+

F

+
+ {lectures.Friday.map((lecture, i) => { + return ( + + ) + })} +
+
) } -const MeetingDisplay = ({ meeting, isHighlighted, onHover }) => ( - - - onHover(meeting.crn)} - onMouseLeave={() => onHover(null)} - > -

- {translateType(meeting.Type)} - {meeting.startTime} -

-

- {meeting.Instructors[0]?.Name} -

-
-
- - - {meeting.Type} - CRN: {meeting.crn} - -

{meeting.startTime} - {meeting.endTime}

-

{meeting.room}

-
-

Instructors: {meeting.Instructors.map(i => i.Name).join(", ") || 'TBA'}

-

Days: {meeting.days}

-

{meeting.StartDate} to {meeting.EndDate}

-
-
-
-); - -export const getCourseData = async (subjectCode, courseCode, title) => { - try { - const semester = "202520"; - const url = "https://api.purdue.io/odata/Courses?$expand=Classes($filter=Term/Code eq '" + - semester + "';$expand=Sections($expand=Meetings($expand=Instructors,Room($expand=Building))))" + - "&$filter=Subject/Abbreviation eq '" + subjectCode + - "' and Number eq '" + courseCode + - "' and contains(Title, '" + encodeURIComponent(title) + "')"; - // console.log(url); - - const response = await fetch(url); - const data = await response.json(); - return data.value[0]; - } catch (e) { - console.error('Error fetching course data:', e); - return null; - } -}; - -// Helper function for type translation -export const translateType = (type) => { - switch (type) { - case "Practice Study Observation": - return "PSO"; - case "Laboratory": - return "Lab"; - default: - return type; +const LectureTimeDisplay = (props) => { + const { lecture } = props; + + const translateType = (type) => { + switch (type) { + case "Practice Study Observation": + return "PSO"; + case "Laboratory": + return "Lab"; + default: + return type; + } } + + return ( + + + {/* If lecture, color background lighter */} + +

+ {translateType(lecture.type) + " - " + lecture.startTime} +

+

+ {lecture.instructors[0]} +

+
+
+ + + {lecture.type} + +

Start Time: {lecture.startTime}

+

Duration: {lecture.duration}

+

Instructors: {lecture.instructors.join(", ")}

+

{lecture.startDate} to {lecture.endDate}

+
+
+
+ ) } -// Helper function to convert 24-hour format to 12-hour format -export const convertTo12HourFormat = (time) => { - const [hour, minute] = time.split(':'); - const period = hour >= 12 ? 'PM' : 'AM'; - const hour12 = hour % 12 || 12; - return `${hour12}:${minute} ${period}`; -}; + export default Calendar; \ No newline at end of file diff --git a/src/components/courseSearch.js b/src/components/courseSearch.js deleted file mode 100644 index 425c0cd..0000000 --- a/src/components/courseSearch.js +++ /dev/null @@ -1,138 +0,0 @@ -import React, { useState, useEffect, useRef } from 'react'; - -const CourseSearch = ({ courses, onSelect, searchTerm, updateFilter }) => { - const [isOpen, setIsOpen] = useState(false); - const [selectedIndex, setSelectedIndex] = useState(-1); - const wrapperRef = useRef(null); - const inputRef = useRef(null); - - // Reset selected index when courses change - useEffect(() => { - setSelectedIndex(-1); - }, [courses]); - - // Close dropdown when clicking outside - useEffect(() => { - updateFilter('semesters', []); - - const handleClickOutside = (event) => { - if ( - wrapperRef.current && - !wrapperRef.current.contains(event.target) && - inputRef.current !== event.target - ) { - setIsOpen(false); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); - - // Handle keyboard navigation - const handleKeyDown = (e) => { - if (!isOpen && courses.length > 0) { - if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { - setIsOpen(true); - return; - } - } - - switch (e.key) { - case 'ArrowDown': - e.preventDefault(); - setSelectedIndex(prev => - prev < Math.min(courses.length - 1, 9) ? prev + 1 : prev - ); - break; - case 'ArrowUp': - e.preventDefault(); - setSelectedIndex(prev => prev > 0 ? prev - 1 : prev); - break; - case 'Enter': - e.preventDefault(); - if (courses.length > 0) { - // If nothing is selected, choose the first result - if (selectedIndex === -1) { - handleSelect(courses[0]); - } else { - handleSelect(courses[selectedIndex]); - } - } - break; - case 'Escape': - setIsOpen(false); - setSelectedIndex(-1); - inputRef.current?.blur(); - break; - default: - return; - } - }; - - const handleSelect = (course) => { - if (!course) return; - onSelect(course); - setIsOpen(false); - setSelectedIndex(-1); - inputRef.current?.blur(); - }; - - const handleInputChange = (e) => { - updateFilter('searchTerm', e.target.value); - setIsOpen(true); - }; - - const shouldShowDropdown = isOpen && searchTerm.trim().length > 0; - - return ( -
-
- setIsOpen(true)} - onKeyDown={handleKeyDown} - className="text-white text-xl bg-neutral-950 w-full pb-2 border-b-2 focus:outline-none focus:border-blue-500 transition duration-300" - placeholder="Search for courses..." - /> -
- - {shouldShowDropdown && ( -
- {courses.length > 0 ? ( - courses.slice(0, 10).map((course, index) => ( -
handleSelect(course)} - onMouseEnter={() => setSelectedIndex(index)} - className={`px-4 py-2 cursor-pointer ${ - selectedIndex === index - ? 'bg-neutral-800 text-white' - : 'text-white hover:bg-neutral-800' - }`} - > -
- {course.value.subjectCode} {course.value.courseCode} -
-
- {course.value.title} -
-
- )) - ) : ( -
- No courses found -
- )} -
- )} -
- ); -}; - -export default CourseSearch; \ No newline at end of file diff --git a/src/components/gpaModal.js b/src/components/gpaModal.js index d4a647f..cea6b55 100644 --- a/src/components/gpaModal.js +++ b/src/components/gpaModal.js @@ -1,16 +1,75 @@ import React, { useEffect, useState } from 'react'; -import { CURRENT_SEMESTER } from '@/hooks/useSearchFilters'; - -const replaceZeroGpaWithDash = (gpaValue) => { - return gpaValue === 0 ? '-' : gpaValue; -}; - const GpaModal = ({ course }) => { + const [gpa, setGpa] = useState({}); + // function to get color based on gpa: + const getColor = (gpa) => { + if (gpa === 0) { + return "#18181b"; + } + + // calculate the color based on gpa as a percentage of 4.0 + const perc = gpa / 4.0; + const perc2 = perc * perc * 0.9; + const color1 = [221, 170, 51]; // higher gpa color + const color2 = [79, 0, 56]; // lower gpa color + + const w1 = perc2; + const w2 = 1 - perc2; + + const r = Math.round(color1[0] * w1 + color2[0] * w2 * 1); + const g = Math.round(color1[1] * w1 + color2[1] * w2 * 1); + const b = Math.round(color1[2] * w1 + color2[2] * w2 * 1); + + const hex = "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); + // console.log(hex); + return hex; + }; + + useEffect(() => { - const grades = processGpaData(course); + if (!course) return; + if (Object.keys(course.gpa).length === 0) return; + + ///////////////////////////////////////////////////// + // set the distributed gpas for each prof and sem + + const grades = {}; + const sems = []; + for (const instructor in course.gpa) { + grades[instructor] = {}; + for (const semester in course.gpa[instructor]) { + if (!sems.includes(semester)) { + sems.push(semester); + } + } + } + + const sorted_sems = sems.sort((a, b) => { + const a_split = a.split(" "); + const b_split = b.split(" "); + if (a_split[1] !== b_split[1]) { + return a_split[1] - b_split[1]; + } + + const seasons = ["Spring", "Summer", "Fall"]; + return seasons.indexOf(a_split[0]) - seasons.indexOf(b_split[0]); + }); + + // all sems should be present in gpa, if it doesnt exist, set it to 0 + for (const instructor in course.gpa) { + for (const semester of sorted_sems) { + if (!course.gpa[instructor][semester]) { + grades[instructor][semester] = { gpa: 0, color: getColor(0) }; + } else { + grades[instructor][semester] = { gpa: course.gpa[instructor][semester][13], color: getColor(course.gpa[instructor][semester][13]) }; + } + } + } + + setGpa(grades); }, [course]); @@ -42,7 +101,7 @@ const GpaModal = ({ course }) => { // console.log(`bg-[${gpa[instructor][semester].color}]`),
-

{replaceZeroGpaWithDash(gpa[instructor][semester].gpa)}

+

{gpa[instructor][semester].gpa}

{semester}

{semester.split(" ")[0]}

@@ -58,144 +117,3 @@ const GpaModal = ({ course }) => { }; export default GpaModal; - -export const ScheduleGpaModal = ({ course }) => { - const [gpa, setGpa] = useState({}); - - useEffect(() => { - const grades = processGpaData(course, true); - - // only show instructors that are teaching in the current semester - if (!course.instructor[CURRENT_SEMESTER]) { - setGpa({}); - return; - } - - for (const instructor in grades) { - if (!course.instructor[CURRENT_SEMESTER].includes(instructor)) { - delete grades[instructor]; - } - } - - setGpa(grades); - }, [course]); - - const semesters = Object.keys(gpa).length != 0 ? Object.keys(gpa[Object.keys(gpa)[0]]) : []; - - return ( -
- {Object.keys(gpa).length != 0 ? - ( - <> -
- {semesters.length > 4 ? semesters.slice(0, 5).map((semester, i) => ( -
-

{semester.split(" ")[0]}

-

{" '" + semester.split(" ")[1].substring(2, 4)}

-
- )) : <> - } -
- {Object.keys(gpa).map((instructor, index) => ( -
-

{instructor}

-
- {Object.keys(gpa[instructor]).map((semester, index) => ( -
-
-

{replaceZeroGpaWithDash(gpa[instructor][semester].gpa)}

-
-
- ))} -
-
- ))} - - ) : ( -
-

No data for current instructors.

-
- )} -
- ); -}; - -// Function to get color based on GPA -export const getColor = (gpa) => { - if (gpa === 0) { - return "#18181b"; - } - - // Calculate the color based on GPA as a percentage of 4.0 - const perc = gpa / 4.0; - const perc2 = perc * perc * 0.9; - const color1 = [221, 170, 51]; // Higher GPA color - const color2 = [79, 0, 56]; // Lower GPA color - - const w1 = perc2; - const w2 = 1 - perc2; - - const r = Math.round(color1[0] * w1 + color2[0] * w2 * 1); - const g = Math.round(color1[1] * w1 + color2[1] * w2 * 1); - const b = Math.round(color1[2] * w1 + color2[2] * w2 * 1); - - const hex = "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); - return hex; -}; - -// Function to process GPA data -const processGpaData = (course, recentOnly = false) => { - if (!course || Object.keys(course.gpa).length === 0) return {}; - - const grades = {}; - const sems = []; - - // Collect all semesters across all instructors - for (const instructor in course.gpa) { - for (const semester in course.gpa[instructor]) { - if (!sems.includes(semester)) { - sems.push(semester); - } - } - } - - // Sort semesters chronologically - const sorted_sems = sems.sort((a, b) => { - const a_split = a.split(" "); - const b_split = b.split(" "); - if (a_split[1] !== b_split[1]) { - return a_split[1] - b_split[1]; // Compare years - } - - const seasons = ["Spring", "Summer", "Fall"]; - return seasons.indexOf(a_split[0]) - seasons.indexOf(b_split[0]); // Compare seasons - }); - - // Process GPA data for each instructor - for (const instructor in course.gpa) { - grades[instructor] = {}; - - // Get recent semesters if flag is true, but keep all semesters with GPA = 0 - const instructorSemesters = sorted_sems; - const recentSemesters = recentOnly - ? instructorSemesters.slice(-5) - : instructorSemesters; - - // Fill in GPA and color for each semester - for (const semester of instructorSemesters) { - if (!recentSemesters.includes(semester)) continue; // Skip if not in the recent subset - - // Fill in GPA data, defaulting to 0 if not present - if (!course.gpa[instructor][semester]) { - grades[instructor][semester] = { gpa: 0, color: getColor(0) }; - } else { - grades[instructor][semester] = { - gpa: course.gpa[instructor][semester][13], - color: getColor(course.gpa[instructor][semester][13]), - }; - } - } - } - - return grades; -}; \ No newline at end of file diff --git a/src/components/graph.js b/src/components/graph.js index 21d1b07..533eda3 100644 --- a/src/components/graph.js +++ b/src/components/graph.js @@ -18,153 +18,65 @@ ChartJS.register( Legend ); -const Graph = ({ data, scheduler = false }) => { - - const chartTitle = scheduler ? '% Grade Distribution Across All Instructors' : '% Grade Distribution'; +const Graph = (props) => { + const { data } = props; return ( <> -
- +
+ + }} data={data} + // { + // { + // labels, + // datasets: [{ + // label: 'test1', + // data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], + // backgroundColor: 'rgba(53, 162, 235, 0.5)', + // }] + // } + // } + /> +
+
) } -export default Graph; - - -// util functions for graph and gpa - -// Helper to sanitize description -export const sanitizeDescription = (data) => { - if (data.description && data.description.startsWith("
{ - const allProfs = []; - for (const semester in instructors) { - for (const instructor of instructors[semester]) { - if (!allProfs.includes(instructor)) { - allProfs.push(instructor); - } - } - } - return allProfs; -}; - -// Helper to calculate GPA and grade distributions -export const calculateGradesAndGPA = (profs, gpaData, colors) => { - const grades = []; - const gpa = {}; - let colorIndex = 0; - - for (const instructor of profs) { - let avgGPA = 0; - let avgGradeDist = Array(13).fill(0); - const color = colors[colorIndex++ % colors.length]; - - if (!gpaData[instructor]) { - gpa[instructor] = [0, "#ffffff"]; - grades.push({ - label: instructor, - data: avgGradeDist, - backgroundColor: "#ffffff", - }); - continue; - } - - let semesterCount = 0; - for (const sem in gpaData[instructor]) { - avgGPA += gpaData[instructor][sem][13]; - avgGradeDist = avgGradeDist.map( - (val, i) => val + gpaData[instructor][sem][i] - ); - semesterCount++; - } - - avgGradeDist = avgGradeDist.map((val) => - Math.round((val / semesterCount) * 100) / 100 - ); - - gpa[instructor] = [ - Math.round((avgGPA / semesterCount) * 100) / 100, - color, - ]; - grades.push({ - label: instructor, - data: avgGradeDist, - backgroundColor: color, - }); - } - return { grades, gpa }; -}; - - -import { graphColors } from '@/lib/utils'; -export const averageAllData = (grades) => { - - const avg = Array(13).fill(0); - for (const grade of grades) { - grade.data.forEach((val, i) => { - avg[i] += val; - }); - } - - const avgData = [{ - label: "Average", - data: avg.map((val) => Math.round(val / grades.length * 100) / 100), - backgroundColor: graphColors[Math.floor(Math.random() * graphColors.length)], - }]; - - return avgData; -} \ No newline at end of file +export default Graph; \ No newline at end of file diff --git a/src/components/prereqs.js b/src/components/prereqs.js index 1e5a797..bfcfe12 100644 --- a/src/components/prereqs.js +++ b/src/components/prereqs.js @@ -1,7 +1,7 @@ import { useRouter } from 'next/router'; import Link from 'next/link'; -const Prereqs = ({ course, scheduler = false }) => { +const Prereqs = ({ course }) => { const router = useRouter(); const parsePrereqs = (prereq, i) => { @@ -19,7 +19,7 @@ const Prereqs = ({ course, scheduler = false }) => { return ( - scheduler ? router.push(`https://www.boilerclasses.com/detail/${detailId}`) : router.push(`/detail/${detailId}`)} + router.push(`/detail/${detailId}`)} className='underline decoration-dotted cursor-pointer hover:text-blue-700 transition-all duration-300 ease-out text-blue-600'> {subjectCode} {courseNumber} @@ -37,7 +37,8 @@ const Prereqs = ({ course, scheduler = false }) => { try { return ( (course.prereqs && course.prereqs[0].split(' ')[0] !== router.query.id) && ( -

+

+ Prerequisites: {course.prereqs.map((prereq, i) => parsePrereqs(prereq, i))}

) diff --git a/src/components/schedule.js b/src/components/schedule.js deleted file mode 100644 index 8083d47..0000000 --- a/src/components/schedule.js +++ /dev/null @@ -1,327 +0,0 @@ -import React, { useState, useEffect, useMemo, useCallback } from 'react'; -import { graphColors } from "@/lib/utils"; -import { useToast } from '@chakra-ui/react'; - -import { stripCourseCode } from '@/pages/detail/[id]'; - -import { getCourseData, convertTo12HourFormat, translateType } from './calendar'; -import ScheduleManager, { processLectureData } from './scheduleManager'; -import { Tooltip } from '@chakra-ui/react'; - -/** - * Converts military time to formatted 12-hour string - */ -export const convertNumberToTime = (timeNum) => { - const hours = Math.floor(timeNum / 100); - const minutes = timeNum % 100; - const period = hours >= 12 ? 'PM' : 'AM'; - const hour12 = hours % 12 || 12; - return `${hour12}:${minutes.toString().padStart(2, '0')} ${period}`; -}; - -const ScheduleCalendar = ({ courses = [], setIsLoading, setSelectedCourse, onCourseRemove }) => { - const toast = useToast(); - const [hoveredCourse, setHoveredCourse] = useState(null); - const [allLectures, setAllLectures] = useState([]); - const [displayedLectures, setDisplayedLectures] = useState([]); - // Memoize course color mapping - const courseColorMap = useMemo(() => new Map(), []); - const [selectedLectureIds, setSelectedLectureIds] = useState(() => { - // Load saved lectures from localStorage on initial render - if (typeof window !== 'undefined') { - const saved = localStorage.getItem('selectedLectures'); - return new Set(saved ? JSON.parse(saved) : []); - } - return new Set(); - }); - - // Add effect to save selected lectures whenever they change - useEffect(() => { - if (typeof window !== 'undefined') { - localStorage.setItem('selectedLectures', JSON.stringify([...selectedLectureIds])); - } - }, [selectedLectureIds]); - - // Add this helper function at the beginning of the component - const getCourseColorIndex = (detailId) => { - if (!courseColorMap.has(detailId)) { - courseColorMap.set(detailId, courseColorMap.size); - } - return courseColorMap.get(detailId); - }; - - const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']; - const times = [ - '', '7 AM', '8 AM', '9 AM', '10 AM', '11 AM', '12 PM', - '1 PM', '2 PM', '3 PM', '4 PM', '5 PM', '6 PM', '7 PM', '8 PM' - ]; - - useEffect(() => { - const fetchCourseData = async () => { - if (!courses.length) { - setIsLoading(false); - setAllLectures([]); // Reset lectures when no courses - setDisplayedLectures([]); // Reset displayed lectures - setSelectedLectureIds(new Set()); - return; - } - - setIsLoading(true); - try { - const coursePromises = courses.map(course => - getCourseData(course.subjectCode, course.courseCode, course.title) - ); - - const rawResults = await Promise.all(coursePromises); - // Pass the full courses array to processLectureData - const processedLectures = processLectureData(rawResults, courses); - - // Keep existing displayed lectures that are still valid - const validLectureIds = new Set(processedLectures.map(lecture => lecture.id)); - const updatedSelectedIds = new Set( - Array.from(selectedLectureIds).filter(id => validLectureIds.has(id)) - ); - - // Find newly pinned courses and select their first lecture - courses.forEach((course) => { - if (course.initialPin) { - // Find all lectures for this course - const courseLectures = processedLectures.filter( - lecture => lecture.courseDetails.detailId === course.detailId - ); - - // Find the first lecture-type meeting - let firstLecture = courseLectures.find(lecture => lecture.type === "Lecture"); - // if not found, select first meeting - if (!firstLecture) { - courseLectures.sort((a, b) => a.start - b.start); - firstLecture = courseLectures[0]; - } - - updatedSelectedIds.add(firstLecture.id); - - delete course.initialPin; - } - }); - - setAllLectures(processedLectures); - setSelectedLectureIds(updatedSelectedIds); - setDisplayedLectures(processedLectures.filter(lecture => updatedSelectedIds.has(lecture.id))); - } catch (error) { - console.error('Error fetching course data:', error); - toast({ - render: () => ( -
- - Something went wrong, please let us know! - - - Report Issue - -
- ), - duration: 5000, - isClosable: true, - position: 'top' - }); - } finally { - setIsLoading(false); - } - }; - - fetchCourseData(); - // Add courses.length and a stringified version of the last course to dependencies - // This ensures the effect runs when courses change or when a course is reselected - }, [courses, courses.length, courses[courses.length - 1]?.tmp_inc]); - - // Optimize lecture selection handler - const handleLectureSelectionChange = useCallback((selectedIds) => { - const selectedSet = new Set(selectedIds); - setSelectedLectureIds(selectedSet); - setDisplayedLectures(allLectures.filter(lecture => selectedSet.has(lecture.id))); - }, [allLectures]); - - /** - * Finds all courses that overlap with a given course on a specific day - */ - const getOverlappingCourses = (day, course) => { - const coursesOnDay = displayedLectures.filter(c => c.day.includes(day)); - const overlappingGroup = new Set(); - - const findOverlaps = (currentCourse) => { - for (const otherCourse of coursesOnDay) { - if (overlappingGroup.has(otherCourse)) continue; - if (!(otherCourse.end <= currentCourse.start || otherCourse.start >= currentCourse.end)) { - overlappingGroup.add(otherCourse); - findOverlaps(otherCourse); - } - } - }; - - findOverlaps(course); - return Array.from(overlappingGroup); - }; - - const calculateTimePosition = (militaryTime) => { - const hours = Math.floor(militaryTime / 100); - const minutes = militaryTime % 100; - return hours + (minutes / 60); - }; - - - const reselectCourseDetails = (overlappingCourse) => { - for (const course of courses) { - for (const crn of course.crn) { - if (crn === parseInt(overlappingCourse.crn)) { - setSelectedCourse(course); - return; - } - } - } - }; - - return ( -
-
- {/* Header Row */} -
-
- {days.map(day => ( -
- {day} -
- ))} -
- - {/* Grid with Lectures */} -
- {/* Day Columns */} - {days.map((day, dayIndex) => ( -
- {/* Time Grid Lines */} - {times.map((time, timeIndex) => ( -
- {dayIndex === 0 && ( // Only display time labels on the first column -
- {time} -
- )} -
- ))} - - {/* Display Lectures for the Day */} -
- {(() => { - // Track which courses we've already rendered - const renderedGroups = new Set(); - - return displayedLectures - .filter(course => course.day.includes(day)) - .map((course, courseIndex) => { - // Skip if this course is part of an already rendered group - if (renderedGroups.has(course.id)) return null; - - const overlaps = getOverlappingCourses(day, course); - - // Sort overlaps by start time and get earliest - const sortedOverlaps = [...overlaps].sort((a, b) => a.start - b.start); - - // Only proceed if this is the earliest course in its group - if (course.id === sortedOverlaps[0].id) { - // Mark all courses in this group as rendered - sortedOverlaps.forEach(c => renderedGroups.add(c.id)); - - return ( -
- {sortedOverlaps.map((overlappingCourse, index) => { - const colorIndex = getCourseColorIndex(overlappingCourse.courseDetails.detailId); - // Calculate position based on THIS course's times, not the original course - const startPos = calculateTimePosition(overlappingCourse.start); - const endPos = calculateTimePosition(overlappingCourse.end); - const top = (startPos - 6) * 2; // Subtract 6 since our grid starts at 6 am technically - const height = (endPos - startPos) * 2; - - const tooltipContent = ( -
-

{overlappingCourse.name}: {overlappingCourse.courseDetails.title}

-

{overlappingCourse.type}

-

{overlappingCourse.startTime} - {convertNumberToTime(overlappingCourse.end)}

-

{overlappingCourse.room}

-
- {overlappingCourse.instructors && ( -

Instructor(s): {overlappingCourse.instructors.join(', ')}

- )} -

Days: {overlappingCourse.day.join(', ')}

-
- ); - - return ( - -
setHoveredCourse(overlappingCourse.courseDetails.detailId)} - onMouseLeave={() => setHoveredCourse(null)} - onClick={() => reselectCourseDetails(overlappingCourse)} - > - {`${translateType(overlappingCourse.type)} ${overlappingCourse.courseDetails.subjectCode}${stripCourseCode(overlappingCourse.courseDetails.courseCode)}`} -
-
- ); - })} -
- ); - } - return null; - }); - })()} -
-
- ))} -
-
- - {/* Manager Section */} -
- -
-
- - ); -}; - -export default ScheduleCalendar; \ No newline at end of file diff --git a/src/components/scheduleManager.js b/src/components/scheduleManager.js deleted file mode 100644 index b320486..0000000 --- a/src/components/scheduleManager.js +++ /dev/null @@ -1,523 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - Checkbox, - Stack, - Modal, - ModalOverlay, - ModalContent, - ModalHeader, - ModalBody, - ModalCloseButton, - useDisclosure, - Button, - Tooltip -} from '@chakra-ui/react'; -import { IoMdOpen, IoIosArrowForward, IoMdClose, IoMdDownload } from "react-icons/io"; - -import { convertTo12HourFormat, translateType } from './calendar'; -import { loadRatingsForProfs, getRMPScore } from '@/components/RMP'; -import { calculateGradesAndGPA, collectAllProfessors } from '@/components/graph'; -import { getColor } from './gpaModal'; -import { graphColors } from '@/lib/utils'; -import { downloadICS } from '@/lib/ics'; - -/** - * Normalizes instructor names between RMP and course data - */ -const normalizeInstructorName = (lectureName, courseInstructors) => { - if (!lectureName) return ""; - const nameParts = lectureName.split(' '); - const firstName = nameParts[0]; - const lastName = nameParts[nameParts.length - 1]; - - return Object.values(courseInstructors) - .flat() - .find(instructor => { - const instructorParts = instructor.split(' '); - return instructorParts[0] === firstName && - instructorParts[instructorParts.length - 1] === lastName; - }) || lectureName; -}; - -/** - * Processes raw lecture data into a standardized format - * @param {Array} courseResults - Raw course data from API - * @param {Array} courses - Course objects from application state - * @returns {Array} Processed lecture data - */ -export const processLectureData = (courseResults, courses) => { - const coursesArray = []; - - courseResults.forEach((courseData, index) => { - if (!courseData?.Classes?.[0]?.Sections) return; - - // Store the full course object for reference - const course = courses[index]; - const courseName = `${course.subjectCode} ${course.courseCode}`; - - courseData.Classes.forEach(classData => { - classData.Sections.forEach(section => { - section.Meetings.forEach(meeting => { - const days = meeting.DaysOfWeek.split(',').map(day => day.trim().slice(0, 3)); - const startTime = meeting.StartTime == null ? '00:00' : meeting.StartTime.split('.')[0]; - const [hours, minutes] = startTime.split(':').map(Number); - const duration = meeting.Duration.replace('PT', ''); - const start = hours * 100 + minutes; - const end = calculateEndTime(startTime, duration); - - // Normalize instructor names before adding to coursesArray - const normalizedInstructors = meeting.Instructors.map(i => - normalizeInstructorName(i.Name, course.instructor) - ); - - coursesArray.push({ - id: meeting.Id, - name: courseName, - classId: classData.Id, - type: meeting.Type, - start, - end, - day: days.includes("Non") ? ["None"] : days, - instructors: normalizedInstructors, - startTime: startTime === '00:00' ? "No Meeting Time" : convertTo12HourFormat(startTime), // if no time, set to No Meeting Time - room: `${meeting.Room.Building.ShortCode}` === 'TBA' ? 'TBA' : `${meeting.Room.Building.ShortCode} ${meeting.Room.Number}`, // if "TBA", set to TBA - duration, - crn: section.Crn, - courseDetails: course, - startDate: meeting.StartDate, - endDate: meeting.EndDate - }); - }); - }); - }); - }); - - return coursesArray; -}; - -const sortByTime = (a, b) => { - // Have "No Meeting Time" show last - if (a === "No Meeting Time") return 1; - if (b === "No Meeting Time") return -1; - - // Convert times to comparable numbers (assuming 12-hour format) - const getTimeValue = (time) => { - const [hour, minute] = time.split(':'); - const isPM = time.includes('PM'); - let hourNum = parseInt(hour); - if (isPM && hourNum !== 12) hourNum += 12; - if (!isPM && hourNum === 12) hourNum = 0; - return hourNum * 60 + parseInt(minute); - }; - - return getTimeValue(a) - getTimeValue(b); -}; - -/** - * Groups lectures by time and sorts them - */ -const groupLecturesByTime = (lectures) => { - const grouped = lectures.reduce((acc, lecture) => { - const timeKey = lecture.startTime; - if (!acc[timeKey]) acc[timeKey] = []; - acc[timeKey].push(lecture); - return acc; - }, {}); - - return Object.fromEntries( - Object.entries(grouped) - .sort(([timeA, _], [timeB, __]) => sortByTime(timeA, timeB)) - ); -}; - -const dayOrder = { - 'Mon': 0, - 'Tue': 1, - 'Wed': 2, - 'Thu': 3, - 'Fri': 4, - 'None': 5 -}; - -const sortByFirstDay = (a, b) => { - const firstDayA = a.day[0] || 'None'; - const firstDayB = b.day[0] || 'None'; - return dayOrder[firstDayA] - dayOrder[firstDayB]; -}; - -/** - * Renders a group of related course sections - */ -const CourseGroup = ({ parentCourse, lectures, selectedLectures, onLectureToggle, setSelectedCourse, onCourseRemove }) => { - const { isOpen, onOpen, onClose } = useDisclosure(); - const [rmpScores, setRmpScores] = useState({}); - const [instructorGPAs, setInstructorGPAs] = useState({}); - - // Load RMP scores and calculate GPAs when modal opens - useEffect(() => { - if (isOpen) { - // Load RMP scores with streaming updates - loadRatingsForProfs(parentCourse, (updatedScores) => { - setRmpScores(updatedScores); - }); - - // Calculate GPAs using graph.js function - const allInstructors = collectAllProfessors(parentCourse.instructor); - - const { gpa } = calculateGradesAndGPA( - Array.from(allInstructors), - parentCourse.gpa, - graphColors - ); - - setInstructorGPAs(gpa); - } - }, [isOpen, parentCourse]); - - const selectedCourseLectures = lectures.filter(lecture => selectedLectures.has(lecture.id)); - const hasSelectedLectures = selectedCourseLectures.length > 0; - - const reselectCourseDetails = () => { - document.getElementById('right_side').scrollIntoView({ behavior: 'smooth', block: 'start' }); - const courseDetails = [...lectures][0].courseDetails; - - // Create a new object with all the properties to trigger React's change detection - const newCourseDetails = { - ...courseDetails, - tmp_inc: Date.now(), // Use Date.now() for a unique value each time - }; - - setSelectedCourse(newCourseDetails); - }; - - // Modified section to process and sort class sections - const classSections = lectures.reduce((acc, lecture) => { - if (!acc[lecture.classId]) { - acc[lecture.classId] = {}; - } - const typeKey = lecture.type; - if (!acc[lecture.classId][typeKey]) { - acc[lecture.classId][typeKey] = {}; - } - const timeKey = lecture.startTime; - if (!acc[lecture.classId][typeKey][timeKey]) { - acc[lecture.classId][typeKey][timeKey] = []; - } - acc[lecture.classId][typeKey][timeKey].push(lecture); - return acc; - }, {}); - - // Sort class sections by type (Lecture first) and then by time - const sortedClassSections = Object.entries(classSections) - .sort(([, typesA], [, typesB]) => { - const getEarliestTimeForType = (types, targetType) => { - if (!types[targetType]) return "99:99"; // Return late time if type doesn't exist - return Object.keys(types[targetType]).sort((a, b) => sortByTime(a, b))[0]; - }; - - // Check for Lecture type first - const hasLectureA = typesA["Lecture"]; - const hasLectureB = typesB["Lecture"]; - if (hasLectureA && !hasLectureB) return -1; - if (!hasLectureA && hasLectureB) return 1; - - // If both have lectures or both don't, sort by earliest time - const earliestTimeA = Object.keys(typesA).reduce((earliest, type) => { - const typeEarliest = getEarliestTimeForType(typesA, type); - return sortByTime(earliest, typeEarliest) < 0 ? earliest : typeEarliest; - }, "99:99"); - - const earliestTimeB = Object.keys(typesB).reduce((earliest, type) => { - const typeEarliest = getEarliestTimeForType(typesB, type); - return sortByTime(earliest, typeEarliest) < 0 ? earliest : typeEarliest; - }, "99:99"); - - return sortByTime(earliestTimeA, earliestTimeB); - }); - - // Add scroll handler when opening modal - const handleModalOpen = () => { - window.scrollTo({ top: 0 }); - onOpen(); - }; - - // Add this new function to handle course removal - const handleRemoveCourse = () => { - const courseDetailId = parentCourse.detailId; - // Remove all lectures associated with this course - lectures.forEach(lecture => { - if (selectedLectures.has(lecture.id)) { - onLectureToggle(lecture.id, lecture.classId); - } - }); - // Remove the course from pinned courses - onCourseRemove(parentCourse.detailId); - }; - - return ( -
- -
-
-
-
- -
-
- {parentCourse.subjectCode} {parentCourse.courseCode} -

{parentCourse.title}

-
-
-
- - -
-
- {hasSelectedLectures ? ( -
- {selectedCourseLectures.map(lecture => ( -
-
-
{`${lecture.type} ${lecture.instructors.length === 0 ? '' : '-'} ${lecture.instructors.join(", ")}`}
-
- {`${lecture.day.join(', ')} • ${lecture.startTime} • ${lecture.room}`} -
-
- -
- ))} -
- ) : ( -
No sections selected
- )} -
- - {/* Modal content remains the same */} - - - - {parentCourse.subjectCode}{parentCourse.courseCode}: {parentCourse.title} - - - - {sortedClassSections.map(([classId, typeGroups], sectionIndex) => ( -
-
Class Section {sectionIndex + 1}
- {Object.entries(typeGroups) - .sort(([typeA], [typeB]) => { - // Sort by type, with Lecture always first - if (typeA === "Lecture") return -1; - if (typeB === "Lecture") return 1; - return typeA.localeCompare(typeB); - }) - .map(([typeKey, timeGroups]) => ( -
-
{typeKey}
- {Object.entries(timeGroups) - .sort(([timeA], [timeB]) => sortByTime(timeA, timeB)) - .map(([timeKey, lectures]) => ( -
-
{timeKey}
-
- {lectures - .sort(sortByFirstDay) - .map((lecture) => ( -
- onLectureToggle(lecture.id, lecture.classId)} - colorScheme="blue" - > - -
- {lecture.day.join(', ')} -
-
- - {/* Instructor mapping, includes gpa and rmp per prof */} - {lecture.instructors.map(instructor => ( -
- {instructor} - {instructorGPAs[instructor]?.[0] > 0 && -
- {`GPA: ${instructorGPAs[instructor][0].toFixed(2)}`} -
- } - {getRMPScore(rmpScores, instructor) && -
- {`RMP: ${getRMPScore(rmpScores, instructor)}`} -
- } -
- ))} -
-
- {translateType(lecture.type)} - {lecture.room} -
-
-
-
- ))} -
-
- ))} -
- ))} -
- ))} -
-
-
-
-
- ); -}; - -// Update ScheduleManager component to handle class-based selection -const ScheduleManager = ({ lectures, selectedLectureIds, onLectureSelectionChange, setSelectedCourse, onCourseRemove }) => { - const [minCredits, setMinCredits] = useState(0); - const [maxCredits, setMaxCredits] = useState(0); - - useEffect(() => { - const credits = Array.from(selectedLectureIds).reduce((acc, id) => { - const lecture = lectures.find(lecture => lecture.id === id); - if (lecture) { - if (!acc[2].has(lecture.courseDetails.detailId)) { - acc[0] += lecture.courseDetails.credits[0] || 0; - acc[1] += lecture.courseDetails.credits[1] || 0; - acc[2].add(lecture.courseDetails.detailId); - } - } - return acc; - }, [0, 0, new Set()]); - - setMinCredits(credits[0]); - setMaxCredits(credits[1]); - }, [selectedLectureIds, lectures]); - - const handleLectureToggle = (lectureId, classId) => { - const newSelectedLectures = new Set(selectedLectureIds); - const clickedLecture = lectures.find(lecture => lecture.id === lectureId); - - // If selecting a new lecture - if (!selectedLectureIds.has(lectureId)) { - // Remove other lectures from the same course (but keep other courses) - lectures - .filter(lecture => lecture.courseDetails.detailId === clickedLecture.courseDetails.detailId) - .forEach(lecture => { - newSelectedLectures.delete(lecture.id); - }); - - // Add the selected lecture and any other lectures from its class - lectures - .filter(lecture => lecture.classId === classId) - .forEach(lecture => { - if (selectedLectureIds.has(lecture.id) || lecture.id === lectureId) { - newSelectedLectures.add(lecture.id); - } - }); - } else { - // If deselecting, just remove this specific lecture - newSelectedLectures.delete(lectureId); - } - - onLectureSelectionChange([...newSelectedLectures]); - }; - - // Group lectures by course name - Update this grouping to use detailId - const courseGroups = lectures.reduce((acc, lecture) => { - const key = lecture.courseDetails.detailId; - if (!acc[key]) { - acc[key] = []; - } - acc[key].push(lecture); - return acc; - }, {}); - - const selectedLectures = lectures.filter(lecture => selectedLectureIds.has(lecture.id)); - - return ( -
-
-
-

Course Sections

- {selectedLectures.length > 0 && ( - -
downloadICS(selectedLectures)}> - -
-
- )} -
-

- Total Credits: {minCredits === maxCredits ? minCredits : `${minCredits} - ${maxCredits}`} -

-
- {courseGroups && Object.keys(courseGroups).length !== 0 ? Object.entries(courseGroups).map(([detailId, courseLectures]) => ( - - )) : ( -

Search for a course by using the search bar, then add a course to show up here!

- )} -
- ); -}; - - -export const calculateEndTime = (startTime, duration) => { - const [hours, minutes] = startTime.split(':').map(Number); - const durationHours = duration.includes('H') ? parseInt(duration.split('H')[0]) : 0; - const durationMinutes = duration.includes('M') ? - parseInt(duration.split('H')[1]?.replace('M', '') || duration.replace('M', '')) : 0; - - let totalMinutes = hours * 60 + minutes + (durationHours * 60) + durationMinutes; - const endHours = Math.floor(totalMinutes / 60); - const endMinutes = totalMinutes % 60; - - return endHours * 100 + endMinutes; -}; - -export default ScheduleManager; \ No newline at end of file diff --git a/src/hooks/useSearchFilters.js b/src/hooks/useSearchFilters.js index 003c8f4..a55f598 100644 --- a/src/hooks/useSearchFilters.js +++ b/src/hooks/useSearchFilters.js @@ -23,16 +23,14 @@ const DEFAULT_FILTERS = { ] }; -export const CURRENT_SEMESTER = "Spring 2025"; - -export const useSearchFilters = () => { +export const useSearchFilters = (currentSemester) => { const router = useRouter(); const { query } = router; // Combined filters state const [filters, setFilters] = useState({ ...DEFAULT_FILTERS, - semesters: [{ label: CURRENT_SEMESTER, value: CURRENT_SEMESTER }], + semesters: [{ label: currentSemester, value: currentSemester }], searchTerm: query.q || '', }); @@ -100,14 +98,7 @@ export const useSearchFilters = () => { }); try { - let baseUrl = window.location.href; - if (baseUrl.includes("localhost")) { - baseUrl = "http://localhost:3000"; - } else { - baseUrl = "https://boilerclasses.com"; - } - - const response = await fetch(`${baseUrl}/api/search?` + params); + const response = await fetch('/api/search?' + params); const data = await response.json(); // Clean up descriptions diff --git a/src/lib/ics.js b/src/lib/ics.js deleted file mode 100644 index 93cf27a..0000000 --- a/src/lib/ics.js +++ /dev/null @@ -1,98 +0,0 @@ -import { CURRENT_SEMESTER } from '@/hooks/useSearchFilters'; - -const formatICSDate = (date) => { - return date.toISOString().replace(/[-:]/g, '').split('.')[0]; -}; - -const parseDate = (dateString) => { - const [year, month, day] = dateString.split('-').map(Number); - return new Date(year, month - 1, day); -}; - -const getNextDayOccurrence = (dayAbbr, afterDate) => { - const days = { 'Mon': 1, 'Tue': 2, 'Wed': 3, 'Thu': 4, 'Fri': 5 }; - const targetDay = days[dayAbbr]; - - let nextOccurrence = new Date(afterDate); - while (nextOccurrence.getDay() !== targetDay) { - nextOccurrence.setDate(nextOccurrence.getDate() + 1); - } - return nextOccurrence; -}; - -export const generateICS = (lectures) => { - let icsContent = [ - 'BEGIN:VCALENDAR', - 'VERSION:2.0', - 'PRODID:-//BoilerClasses//Schedule//EN', - 'CALSCALE:GREGORIAN', - 'METHOD:PUBLISH', - 'BEGIN:VTIMEZONE', - 'TZID:America/New_York', - 'BEGIN:STANDARD', - 'DTSTART:20071104T020000', - 'RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11', - 'TZOFFSETFROM:-0400', - 'TZOFFSETTO:-0500', - 'END:STANDARD', - 'BEGIN:DAYLIGHT', - 'DTSTART:20070311T020000', - 'RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3', - 'TZOFFSETFROM:-0500', - 'TZOFFSETTO:-0400', - 'END:DAYLIGHT', - 'END:VTIMEZONE' - ]; - - lectures.forEach(lecture => { - const semesterStart = parseDate(lecture.startDate); - const semesterEnd = parseDate(lecture.endDate); - - lecture.day.forEach(day => { - if (day === 'None') return; - - const startDate = getNextDayOccurrence(day, semesterStart); - const [startHour, startMinute] = lecture.startTime.match(/(\d+):(\d+)/).slice(1); - startDate.setHours( - lecture.startTime.includes('PM') && startHour !== '12' - ? parseInt(startHour) + 12 - : parseInt(startHour) - ); - startDate.setMinutes(parseInt(startMinute)); - - const endDate = new Date(startDate); - const durationMatch = lecture.duration.match(/(?:(\d+)H)?(?:(\d+)M)?/); - const hours = parseInt(durationMatch[1] || 0); - const minutes = parseInt(durationMatch[2] || 0); - endDate.setHours(endDate.getHours() + hours); - endDate.setMinutes(endDate.getMinutes() + minutes); - - icsContent = icsContent.concat([ - 'BEGIN:VEVENT', - `UID:${lecture.id}-${day}@boilerclasses.com`, - `DTSTAMP:${formatICSDate(new Date())}`, - `DTSTART;TZID=America/New_York:${formatICSDate(startDate)}`, - `DTEND;TZID=America/New_York:${formatICSDate(endDate)}`, - `RRULE:FREQ=WEEKLY;UNTIL=${formatICSDate(semesterEnd)}`, - `SUMMARY:${lecture.name} - ${lecture.type}`, - `LOCATION:${lecture.room}`, - `DESCRIPTION:${lecture.type}\\nInstructor(s): ${lecture.instructors.join(', ')}`, - 'END:VEVENT' - ]); - }); - }); - - icsContent.push('END:VCALENDAR'); - return icsContent.join('\r\n'); -}; - -export const downloadICS = (lectures) => { - const icsContent = generateICS(lectures); - const blob = new Blob([icsContent], { type: 'text/calendar;charset=utf-8' }); - const link = document.createElement('a'); - link.href = window.URL.createObjectURL(blob); - link.setAttribute('download', `${CURRENT_SEMESTER}_BoilerClasses.ics`); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); -}; \ No newline at end of file diff --git a/src/middleware.js b/src/middleware.js deleted file mode 100644 index 19ffc52..0000000 --- a/src/middleware.js +++ /dev/null @@ -1,60 +0,0 @@ -// middleware.js - -import { NextResponse } from 'next/server'; - -// RegExp for public files -const PUBLIC_FILE = /\.(.*)$/; // Files - -// Define allowed subdomains -const ALLOWED_SUBDOMAINS = ['schedule']; - -// CORS headers, allow only subdomains -const corsHeaders = { - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type' -}; -for (const subdomain of ALLOWED_SUBDOMAINS) { - corsHeaders[`Access-Control-Allow-Origin`] = `https://${subdomain}.boilerclasses.com`; - corsHeaders[`Access-Control-Allow-Origin`] = `http://${subdomain}.localhost:3000`; -} - - -// Middleware function -export async function middleware(req) { - // Clone the URL - const url = req.nextUrl.clone(); - - // Skip public files - if (PUBLIC_FILE.test(url.pathname) || url.pathname.includes('_next')) return; - - const host = req.headers.get('host'); - const subdomain = getValidSubdomain(host); - if (subdomain) { - // Subdomain available, rewriting: ${url.pathname} to /${subdomain}${url.pathname} - url.pathname = `/${subdomain}${url.pathname}`; - } - - // Create a response with CORS headers - const response = NextResponse.rewrite(url); - for (const [key, value] of Object.entries(corsHeaders)) { - response.headers.set(key, value); - } - - return response; -} - -const getValidSubdomain = (host) => { - let subdomain = null; - if (!host && typeof window !== 'undefined') { - // On client side, get the host from window - host = window.location.host; - } - if (host && host.includes('.')) { - const candidate = host.split('.')[0]; - if (candidate && !candidate.includes('localhost') && ALLOWED_SUBDOMAINS.includes(candidate)) { - // Valid candidate - subdomain = candidate; - } - } - return subdomain; -}; \ No newline at end of file diff --git a/src/pages/detail/[id].js b/src/pages/detail/[id].js index db49665..930954d 100644 --- a/src/pages/detail/[id].js +++ b/src/pages/detail/[id].js @@ -34,12 +34,12 @@ import "react-circular-progressbar/dist/styles.css"; // ----- Component imports ----- -import { instructorStyles, graphColors, boilerExamsCourses, labels, genedsOptions } from '@/lib/utils'; +import { instructorStyles, graphColors, boilerExamsCourses, labels } from '@/lib/utils'; import { semesters, subjects } from "@/lib/utils" import Footer from '@/components/footer'; import Calendar from '@/components/calendar'; -import Graph, { sanitizeDescription, collectAllProfessors, calculateGradesAndGPA } from '@/components/graph'; +import Graph from '@/components/graph'; import GpaModal from '@/components/gpaModal'; import FullInstructorModal from '@/components/fullInstructorModal'; import Prereqs from '@/components/prereqs'; @@ -54,45 +54,103 @@ const CardDetails = ({ courseData, semData }) => { // UseEffect that loads on first render useEffect(() => { if (!courseData) return; + // console.log(JSON.stringify(courseData, null, 2)); // for debugging and you dont wanna start server - // Helper to handle initial instructor setup - const setupInitialInstructor = (allProfs, semesterProfs, semester) => { - const firstProf = - semesterProfs?.length > 0 ? semesterProfs[0] : allProfs[0]; - refreshGraph({ value: firstProf, label: firstProf }); - setSelectedInstructors([firstProf]); - }; + // set descriptions to none if it's html + if (courseData.description && courseData.description.startsWith(" 0) { + const firstProf = currentSemesterProfs[0]; + refreshGraph({ value: firstProf, label: firstProf }); + setSelectedInstructors([firstProf]); + } else { + // Fallback if no professors are found in the current semester + const firstProf = allProfs[0]; + refreshGraph({ value: firstProf, label: firstProf }); + setSelectedInstructors([firstProf]); + } setLoading(false); - }, [router.isReady, courseData]); + }, [router.isReady, courseData]); // Another UseEffect to asynchronously get RMP ratings @@ -179,11 +237,33 @@ const CardDetails = ({ courseData, semData }) => { // Function to replace gened codes with actual names const genedCodeToName = (code) => { + const genedsOptions = [ + { label: "Behavioral/Social Science", value: "BSS" }, + { label: "Civics Literacy", value: "Civics Literacy" }, + { label: "Humanities", value: "Humanities" }, + { label: "JEDI", value: "JEDI" }, + { label: "Oral Communications", value: "OC" }, + { label: "Information Literacy", value: "IL" }, + { label: "Quantitative Reasoning", value: "QR" }, + { label: "Science", value: "Science" }, + { label: "Science Technology and Society", value: "STS" }, + { label: "Written Communication", value: "WC" } + ] + const gened = genedsOptions.filter(gened => gened.value === code); return gened[0].label; } + // function to strip courseData code to remove the 00s + function stripCourseCode(courseCode) { + let formattedName = courseCode.toString(); + if (/\d{5}$/.test(formattedName) && formattedName.slice(-2) === "00") { + formattedName = formattedName.slice(0, -2); + } + return formattedName; + } + /////////////////////////////////////// RENDER ///////////////////////////////////////// if (JSON.stringify(courseData) == '{}') { @@ -347,10 +427,7 @@ const CardDetails = ({ courseData, semData }) => {

Course {courseData.subjectCode} {stripCourseCode(courseData.courseCode)} from Purdue University - West Lafayette.

{/* Prerequisites */} -
- Prerequisites:  - -
+ @@ -489,9 +566,7 @@ const CardDetails = ({ courseData, semData }) => { {/* GPA Graph */} {defaultGPA.datasets && Array.isArray(defaultGPA.datasets) && defaultGPA.datasets.length > 0 && ( -
- -
+ )} {!(defaultGPA.datasets && Array.isArray(defaultGPA.datasets) && defaultGPA.datasets.length > 0) && ( @@ -529,17 +604,6 @@ const CardDetails = ({ courseData, semData }) => { }; -export default CardDetails; - - -// function to strip courseData code to remove the 00s -export function stripCourseCode(courseCode) { - let formattedName = courseCode.toString(); - if (/\d{5}$/.test(formattedName) && formattedName.slice(-2) === "00") { - formattedName = formattedName.slice(0, -2); - } - return formattedName; -} // @Sarthak made this, some api call to get courseData data @@ -570,3 +634,5 @@ export async function getServerSideProps(context) { }, } } + +export default CardDetails; \ No newline at end of file diff --git a/src/pages/index.js b/src/pages/index.js index cbbce58..3dbc8a4 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -12,6 +12,7 @@ import SearchFilters from '@/components/searchFilters'; import { useSearchFilters } from '@/hooks/useSearchFilters'; const inter = Inter({ subsets: ['latin'] }); +const currentSemester = "Spring 2025"; const CourseCatalog = () => { @@ -24,7 +25,7 @@ const CourseCatalog = () => { setFiltersCollapsed, courses, transformQuery - } = useSearchFilters(); + } = useSearchFilters(currentSemester); // Gets all the filters as a string for displaying to users const getAllFiltersString = () => { @@ -101,14 +102,14 @@ const CourseCatalog = () => { - + - + diff --git a/src/pages/schedule/index.js b/src/pages/schedule/index.js deleted file mode 100644 index 77b8077..0000000 --- a/src/pages/schedule/index.js +++ /dev/null @@ -1,344 +0,0 @@ -// schedule.boilerclasses.com - -// Group external imports -import Head from 'next/head'; -import Link from 'next/link'; -import { Inter } from 'next/font/google'; -import { useState, useEffect, useMemo } from 'react'; -import { Spinner, Tooltip } from '@chakra-ui/react' -import { IoMdOpen, IoMdTrash, IoIosClose, IoIosWarning } from "react-icons/io"; - -// Group internal imports -import { useSearchFilters, CURRENT_SEMESTER } from '@/hooks/useSearchFilters'; -import { genedsOptions, labels, graphColors } from '@/lib/utils'; -import CourseSearch from '@/components/courseSearch'; -import ScheduleCalendar from '@/components/schedule'; -import Graph, { sanitizeDescription, collectAllProfessors, calculateGradesAndGPA, averageAllData } from '@/components/graph'; -import { ScheduleGpaModal } from '@/components/gpaModal'; -import Prereqs from '@/components/prereqs'; -import Footer from '@/components/footer'; - -const inter = Inter({ subsets: ['latin'] }); - -const Schedule = () => { - const { - updateFilter, - courses, - filters, - } = useSearchFilters(); - - const [selectedCourse, setSelectedCourse] = useState(null); - const [gpaGraph, setGpaGraph] = useState({ - labels, - datasets: [] - }); - - const [pinCourses, setPinCourses] = useState(() => { - // Load saved courses from localStorage on initial render - if (typeof window !== 'undefined') { - const saved = localStorage.getItem('pinnedCourses'); - return saved ? JSON.parse(saved) : []; - } - return []; - }); - - const [isLoading, setIsLoading] = useState(false); - - // Add effect to save pinned courses whenever they change - useEffect(() => { - if (typeof window !== 'undefined') { - localStorage.setItem('pinnedCourses', JSON.stringify(pinCourses)); - } - }, [pinCourses]); - - // Memoize graph data calculation - const graphData = useMemo(() => { - if (!selectedCourse) return null; - - sanitizeDescription(selectedCourse); - const allProfs = collectAllProfessors(selectedCourse.instructor); - const { grades } = calculateGradesAndGPA(allProfs, selectedCourse.gpa, graphColors); - return { - labels, - datasets: averageAllData(grades), - }; - }, [selectedCourse]); - - useEffect(() => { - if (!graphData) return; - setGpaGraph(graphData); - - // Highlight course details section - const highlightCourseDiv = async () => { - const courseDiv = document.getElementById("course_details"); - courseDiv.classList.add("ring-4", "ring-yellow-500"); - await new Promise(resolve => setTimeout(resolve, 500)); - courseDiv.classList.remove("ring-4", "ring-yellow-500"); - }; - - highlightCourseDiv(); - }, [graphData]); - - // When selecting an item - const handleOnSelect = (course) => { - if (!course) return; - updateFilter('searchTerm', ''); - setSelectedCourse(course.value); - }; - - const handleCourseRemove = (detailId) => { - setPinCourses(prevCourses => prevCourses.filter(course => course.detailId !== detailId)); - }; - - return ( - <> - - Schedule Assistant - BoilerClasses - - - - - - - - - - -
- {/* Left Side */} -
- {/* Logo */} -
- - BoilerClasses Logo -

- BoilerClasses -

- -
- - {/* mobile message */} -
-
document.getElementById('mobile_msg').classList.add('hidden')}>
- Use the Scheduling Assistant on Desktop for the best experience! -
- -
- -
-
- - {/* Right Side */} -
-
- -
- {selectedCourse === null ? ( -
-

Welcome to BoilerClasses

-

- Utilize the scheduling assistant to plan your semester. Use the search bar to find courses, and add them to show up in the schedule! -

-
- ) : ( - <> - {/* Selected Course */} -
-
- - {/* LEFT SIDE - Course Info */} -
-

{selectedCourse.subjectCode} {selectedCourse.courseCode}

-

{selectedCourse.title}

- - {/* Description Display */} -

{selectedCourse.description}

-
- - {/* RIGHT SIDE - Course Details */} -
- -
- - {/* Open in details button */} - -
- - Open Details -
-
- - {/* Add Course button */} - {selectedCourse.instructor[CURRENT_SEMESTER] && selectedCourse.instructor[CURRENT_SEMESTER].length > 0 ? ( - pinCourses.some(course => course.detailId === selectedCourse.detailId) ? ( -
setPinCourses(pinCourses.filter(course => course.detailId !== selectedCourse.detailId))} - > - {isLoading ?
: } -
- ) : ( -
{ - setPinCourses([...pinCourses, { - ...selectedCourse, - initialPin: true // Add this flag to indicate it's a new pin - }]); - }} - > - {isLoading ?
: 'Add Course'} -
- )) : ( - -
- -
-
- )} - -
- -
- - {/* Course Type Display */} -
-

Course Type

- -
- {/* Schedule Type Display */} - {selectedCourse.sched.map((s, i) => ( - - {s} - - ))} - - - {/* Gened Type Display */} - {selectedCourse.gened.length > 0 && selectedCourse.gened.map((gened, i) => ( - - {genedsOptions.filter(x => x.value === gened)[0]?.label || ''} - - ))} -
-
- - - {/* Credits Display */} -
- Credits -

- {selectedCourse.credits[0] === selectedCourse.credits[1] - ? `${selectedCourse.credits[0]}` - : `${selectedCourse.credits[0]} - ${selectedCourse.credits[1]}`} -

-
- -
- - - - {/* Instructors Display */} -
-

Instructors

-
- - - {selectedCourse.instructor[CURRENT_SEMESTER] ? ( - selectedCourse.instructor[CURRENT_SEMESTER].map((prof, i) => ( - - - event.stopPropagation()} - > - {prof} - - {i < selectedCourse.instructor[CURRENT_SEMESTER].length - 1 && ", "} - - )) - ) : ( -

No instructors listed for {CURRENT_SEMESTER}

- )} -
-
- - {/* Prerequisites Display */} - {selectedCourse.prereqs && ( -
-

Prerequisites

- -
- )} - - {/* gpaModal */} -
-

GPA Avg Per Semester

- -
- -
-
- {/*

{selectedCourse.gpa[""]}

*/} - - - {/* Graph */} -
- - {gpaGraph.datasets ? ( -
- -
- ) : ( -
-
-

- No grade data available for this course. -

-
-
- )} - -
-
- - )} -
-
-