diff --git a/nsysu_selector_helper/package.json b/nsysu_selector_helper/package.json index a27dd2f..58c95ca 100644 --- a/nsysu_selector_helper/package.json +++ b/nsysu_selector_helper/package.json @@ -10,20 +10,25 @@ "preview": "vite preview" }, "dependencies": { + "@ant-design/icons": "^5.3.7", + "@swc/plugin-styled-components": "^2.0.9", "antd": "^5.19.0", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "styled-components": "^6.1.11" }, "devDependencies": { "@types/node": "^20.14.9", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@types/styled-components": "^5.1.34", "@typescript-eslint/eslint-plugin": "^7.13.1", "@typescript-eslint/parser": "^7.13.1", "@vitejs/plugin-react-swc": "^3.5.0", "eslint": "^8.57.0", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", + "prettier": "^3.3.2", "typescript": "^5.2.2", "vite": "^5.3.1" } diff --git a/nsysu_selector_helper/src/App.tsx b/nsysu_selector_helper/src/App.tsx index 1ad76d3..1213a79 100644 --- a/nsysu_selector_helper/src/App.tsx +++ b/nsysu_selector_helper/src/App.tsx @@ -3,6 +3,7 @@ import { ConfigProvider, Button, List, Typography, Select } from 'antd'; import { useThemeConfig } from '@/hooks/useThemeConfig'; import { NSYSUCourseAPI } from '@/api/NSYSUCourseAPI'; import { Course, AcademicYear } from '@/types'; +import SectionHeader from "#/SectionHeader.tsx"; const { Option } = Select; @@ -49,49 +50,7 @@ const App: FC = () => { return ( -
- Course Viewer - - - Total: {courses.length} - ( - - {course.id} {course.name} - {course.teacher} ({course.credit} credits) - - )} - style={{ marginTop: '20px' }} - /> -
+
); }; diff --git a/nsysu_selector_helper/src/api/NSYSUCourseAPI.ts b/nsysu_selector_helper/src/api/NSYSUCourseAPI.ts index 22633ca..049081e 100644 --- a/nsysu_selector_helper/src/api/NSYSUCourseAPI.ts +++ b/nsysu_selector_helper/src/api/NSYSUCourseAPI.ts @@ -3,6 +3,9 @@ import { AcademicYear, Course, SemesterUpdate } from '@/types'; const BASE_URL = 'https://whats2000.github.io/NSYSUCourseAPI'; export class NSYSUCourseAPI { + /** + * 取得所有可用學期列表 + */ static async getAvailableSemesters(): Promise { const response = await fetch(`${BASE_URL}/version.json`); if (!response.ok) { @@ -11,6 +14,10 @@ export class NSYSUCourseAPI { return response.json(); } + /** + * 取得指定學年度的學期更新資訊 + * @param academicYear - 學年度 + */ static async getSemesterUpdates(academicYear: string): Promise { const response = await fetch(`${BASE_URL}/${academicYear}/version.json`); if (!response.ok) { @@ -19,6 +26,11 @@ export class NSYSUCourseAPI { return response.json(); } + /** + * 取得指定學年度、更新時間的所有課程 + * @param academicYear - 學年度 + * @param updateTime - 更新時間 + */ static async getCourses(academicYear: string, updateTime: string): Promise { const response = await fetch(`${BASE_URL}/${academicYear}/${updateTime}/all.json`); if (!response.ok) { @@ -31,6 +43,9 @@ export class NSYSUCourseAPI { }); } + /** + * 取得最新學期的所有課程 + */ static async getLatestCourses(): Promise { const semesters = await NSYSUCourseAPI.getAvailableSemesters(); const latestAcademicYear = semesters.latest; diff --git a/nsysu_selector_helper/src/assets/banner.svg b/nsysu_selector_helper/src/assets/banner.svg new file mode 100644 index 0000000..c169a7d --- /dev/null +++ b/nsysu_selector_helper/src/assets/banner.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nsysu_selector_helper/src/components/SectionHeader.tsx b/nsysu_selector_helper/src/components/SectionHeader.tsx new file mode 100644 index 0000000..3255f5f --- /dev/null +++ b/nsysu_selector_helper/src/components/SectionHeader.tsx @@ -0,0 +1,93 @@ +import { FC } from 'react'; +import { Flex, Menu, MenuProps, theme } from 'antd'; +import { + BookFilled, + BookOutlined, + FileDoneOutlined, + FileSearchOutlined, + NotificationOutlined, +} from '@ant-design/icons'; +import banner from '@/assets/banner.svg'; +import styled from 'styled-components'; + +type HeaderProps = {}; + +const HeaderContainer = styled(Flex)<{ + $primaryColor: string; + $textColor: string; +}>` + padding: 0 20px; + + background-color: ${(props) => props.$primaryColor}; + color: ${(props) => props.$textColor}; +`; + +const StyledMenu = styled(Menu)<{ + $textColor: string; +}>` + padding-bottom: 5px; + background: transparent; + + li.ant-menu-item { + background-color: transparent; + color: ${(props) => props.$textColor} !important; + } + + li.ant-menu-item-selected::after { + border-bottom-color: ${(props) => props.$textColor} !important; + } + + li.ant-menu-item-active::after { + border-bottom-color: ${(props) => props.$textColor} !important; + } +`; + +const Brand = styled.img` + height: 36px; + margin-right: 20px; + + @media (max-width: 290px) { + display: none; + } +`; + +const SectionHeader: FC = () => { + const { token } = theme.useToken(); + const primaryColor = token.colorPrimary; + const textColor = '#ffffff'; + + const navTabs: MenuProps['items'] = [ + { key: 'all-courses', label: '所有課程', icon: }, + { key: 'semester-compulsory', label: '學期必修', icon: }, + { + key: 'course-detective', + label: '課程偵探', + icon: , + }, + { key: 'selected-export', label: '已選匯出', icon: }, + { key: 'announcements', label: '公告', icon: }, + ]; + + return ( + + + + + + + ); +}; + +export default SectionHeader; diff --git a/nsysu_selector_helper/vite.config.ts b/nsysu_selector_helper/vite.config.ts index d29803f..3751a69 100644 --- a/nsysu_selector_helper/vite.config.ts +++ b/nsysu_selector_helper/vite.config.ts @@ -4,7 +4,11 @@ import react from '@vitejs/plugin-react-swc' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react({ + plugins: [ + ['@swc/plugin-styled-components', { displayName: false }], + ], + })], resolve: { alias: [ {find: '@', replacement: resolve(__dirname, 'src')}, diff --git a/nsysu_selector_helper/yarn.lock b/nsysu_selector_helper/yarn.lock index b868633..137bfe1 100644 --- a/nsysu_selector_helper/yarn.lock +++ b/nsysu_selector_helper/yarn.lock @@ -29,7 +29,7 @@ "@ant-design/icons@^5.3.7": version "5.3.7" - resolved "https://registry.npmjs.org/@ant-design/icons/-/icons-5.3.7.tgz" + resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-5.3.7.tgz#d9f3654bf7934ee5faba43f91b5a187f5309ec68" integrity sha512-bCPXTAg66f5bdccM4TT21SQBDO1Ek2gho9h3nO9DAKXJP4sq+5VBjrQMSxMVXSB3HyEz+cUbHQ5+6ogxCOpaew== dependencies: "@ant-design/colors" "^7.0.0" @@ -66,6 +66,23 @@ resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== +"@emotion/is-prop-valid@1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337" + integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw== + dependencies: + "@emotion/memoize" "^0.8.1" + +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + +"@emotion/unitless@0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + "@emotion/unitless@^0.7.5": version "0.7.5" resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz" @@ -494,6 +511,13 @@ resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz" integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== +"@swc/plugin-styled-components@^2.0.9": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@swc/plugin-styled-components/-/plugin-styled-components-2.0.9.tgz#e65d8e4af5e4f1f2eb86d350e217b3510a83ccd6" + integrity sha512-0aPv7lNed27qs8JBklLkVSlLhpPRU3YKRnKplObaAyhNWbpbOkCbVSTay5ArFT2Gz1rz844Np7l4DMozEtZRBg== + dependencies: + "@swc/counter" "^0.1.3" + "@swc/types@^0.1.9": version "0.1.9" resolved "https://registry.npmjs.org/@swc/types/-/types-0.1.9.tgz" @@ -506,6 +530,14 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/hoist-non-react-statics@*": + version "3.3.5" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494" + integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + "@types/node@^20.14.9": version "20.14.9" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.9.tgz#12e8e765ab27f8c421a1820c99f5f313a933b420" @@ -533,6 +565,20 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/styled-components@^5.1.34": + version "5.1.34" + resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.34.tgz#4107df8ef8a7eaba4fa6b05f78f93fba4daf0300" + integrity sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA== + dependencies: + "@types/hoist-non-react-statics" "*" + "@types/react" "*" + csstype "^3.0.2" + +"@types/stylis@4.2.5": + version "4.2.5" + resolved "https://registry.yarnpkg.com/@types/stylis/-/stylis-4.2.5.tgz#1daa6456f40959d06157698a653a9ab0a70281df" + integrity sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw== + "@typescript-eslint/eslint-plugin@^7.13.1": version "7.15.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.15.0.tgz" @@ -759,6 +805,11 @@ callsites@^3.0.0: resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +camelize@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" + integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== + chalk@^4.0.0: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" @@ -810,7 +861,21 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" -csstype@^3.0.2, csstype@^3.1.3: +css-color-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" + integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== + +css-to-react-native@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32" + integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== + dependencies: + camelize "^1.0.0" + css-color-keywords "^1.0.0" + postcss-value-parser "^4.0.2" + +csstype@3.1.3, csstype@^3.0.2, csstype@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== @@ -1114,6 +1179,13 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +hoist-non-react-statics@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + ignore@^5.2.0, ignore@^5.3.1: version "5.3.1" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz" @@ -1342,7 +1414,7 @@ path-type@^4.0.0: resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -picocolors@^1.0.1: +picocolors@^1.0.0, picocolors@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== @@ -1352,6 +1424,20 @@ picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +postcss-value-parser@^4.0.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@8.4.38: + version "8.4.38" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.2.0" + postcss@^8.4.38: version "8.4.39" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz" @@ -1366,6 +1452,11 @@ prelude-ls@^1.2.1: resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prettier@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a" + integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA== + punycode@^2.1.0: version "2.3.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" @@ -1735,6 +1826,11 @@ react-dom@^18.3.1: loose-envify "^1.1.0" scheduler "^0.23.2" +react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + react-is@^18.2.0: version "18.3.1" resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" @@ -1825,6 +1921,11 @@ semver@^7.6.0: resolved "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz" integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== +shallowequal@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" @@ -1864,7 +1965,22 @@ strip-json-comments@^3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -stylis@^4.0.13: +styled-components@^6.1.11: + version "6.1.11" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-6.1.11.tgz#01948e5195bf1d39e57e0a85b41958c80e40cfb8" + integrity sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA== + dependencies: + "@emotion/is-prop-valid" "1.2.2" + "@emotion/unitless" "0.8.1" + "@types/stylis" "4.2.5" + css-to-react-native "3.2.0" + csstype "3.1.3" + postcss "8.4.38" + shallowequal "1.1.0" + stylis "4.3.2" + tslib "2.6.2" + +stylis@4.3.2, stylis@^4.0.13: version "4.3.2" resolved "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz" integrity sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg== @@ -1903,6 +2019,11 @@ ts-api-utils@^1.3.0: resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz" integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== +tslib@2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz"