diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..ba97997 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,37 @@ +--- +name: Bug report +about: Create a report to help us fix bugs +title: '[BUG]' +labels: bug +--- + +## 설명 \* + +A clear and concise description of what the bug is. + +## 재현 순서 \* + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +## 테스트 환경 \* + +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Web Version: [e.g. 1.1.0] + +## 스크린샷 + +If applicable, add screenshots to help explain your problem. + +## 에러 로그 + +```sh +(OPTIONAL) +``` + +## 추가 정보 + +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 0000000..e00231d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,28 @@ +--- +name: Feature +about: Suggest an idea for this project +title: '[FEAT]' +labels: feat +--- + +## 동기 \* + +A clear and concise description of what the motivation is. + +## 제안 내용 \* + +A clear and concise description of what you want to happen. + +## 스크린샷 + +If applicable, add screenshots to help explain your feature request. + +## 테스트 환경 + +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Web Version: [e.g. 1.1.0] + +## 추가 정보 + +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE/migration.md b/.github/PULL_REQUEST_TEMPLATE/migration.md new file mode 100644 index 0000000..b03ec02 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/migration.md @@ -0,0 +1,16 @@ +--- +name: Migration +about: Migrate to TypeScript and refactor components +title: 'Migrate ...' +labels: migrate +--- + +## Description + +PR의 목적, 내용 요약 등을 간단히 작성합니다. + +## Checklist + +PR에 포함된 task 목록을 간단히 작성합니다. + +- [ ] 없음 diff --git a/src/components/blocks/CourseBlock.jsx b/src/components/blocks/CourseBlock.jsx deleted file mode 100644 index f960d5b..0000000 --- a/src/components/blocks/CourseBlock.jsx +++ /dev/null @@ -1,93 +0,0 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; -import PropTypes from 'prop-types'; -import { withTranslation } from 'react-i18next'; - -import { appBoundClassNames as classNames } from '../../common/boundClassNames'; -import { getProfessorsFullStr } from '../../utils/courseUtils'; - -import courseShape from '../../shapes/model/subject/CourseShape'; -import linkShape from '../../shapes/LinkShape'; -import Attributes from '../Attributes'; - -const CourseBlock = ({ - t, - course, - shouldShowReadStatus, - isRead, - isRaised, - isDimmed, - onMouseOver, - onMouseOut, - onClick, - linkTo, -}) => { - const handleMouseOver = onMouseOver - ? (event) => { - onMouseOver(course); - } - : null; - const handleMouseOut = onMouseOut - ? (event) => { - onMouseOut(course); - } - : null; - const handleClick = onClick - ? (event) => { - onClick(course); - } - : null; - - const RootTag = linkTo ? Link : 'div'; - - return ( - -
- {!shouldShowReadStatus ? null : isRead ? ( - - ) : ( - - )} - {course[t('js.property.title')]} -   - {course.old_code} -
- -
- ); -}; - -CourseBlock.propTypes = { - course: courseShape.isRequired, - shouldShowReadStatus: PropTypes.bool, - isRead: PropTypes.bool, - isRaised: PropTypes.bool, - isDimmed: PropTypes.bool, - onMouseOver: PropTypes.func, - onMouseOut: PropTypes.func, - onClick: PropTypes.func, - linkTo: linkShape, -}; - -export default withTranslation()(React.memo(CourseBlock)); diff --git a/src/components/blocks/CourseBlock.tsx b/src/components/blocks/CourseBlock.tsx new file mode 100644 index 0000000..e4e06f9 --- /dev/null +++ b/src/components/blocks/CourseBlock.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { withTranslation } from 'react-i18next'; +import { appBoundClassNames as classNames } from '@/common/boundClassNames'; +import { getProfessorsFullStr } from '@/utils/courseUtils'; +import Course from '@/shapes/model/subject/Course'; +import BlockLink from '@/shapes/BlockLink'; +import Attributes from '@/components/Attributes'; +import { useTranslatedString } from '@/hooks/useTranslatedString'; + +interface Props { + t: (string: string) => string; + course: Course; + shouldShowReadStatus?: boolean; + isRead?: boolean; + isRaised?: boolean; + isDimmed?: boolean; + onMouseOver?: (course: Course) => void; + onMouseOut?: (course: Course) => void; + onClick?: (course: Course) => void; + linkTo?: BlockLink; +} + +/** + * Component `CourseBlock` displays an overview of a course within the search results on the `DictionaryPage`. + * It shows the title, classification, professors, and description of the course. + */ +const CourseBlock: React.FC = ({ + t, + course, + shouldShowReadStatus, + isRead, + isRaised, + isDimmed, + onMouseOver, + onMouseOut, + onClick, + linkTo, +}) => { + const translate = useTranslatedString(); + + const RootTag = linkTo ? Link : 'div'; + + return ( + onClick?.(course)} + onMouseOver={() => onMouseOver?.(course)} + onMouseOut={() => onMouseOut?.(course)} + to={linkTo ?? ''}> +
+ {!shouldShowReadStatus ? null : isRead ? ( + + ) : ( + + )} + {translate(course, 'title')} +   + {course.old_code} +
+ +
+ ); +}; + +export default withTranslation()(React.memo(CourseBlock)); diff --git a/src/components/blocks/CourseSimpleBlock.jsx b/src/components/blocks/CourseSimpleBlock.jsx deleted file mode 100644 index 6de48b2..0000000 --- a/src/components/blocks/CourseSimpleBlock.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { withTranslation } from 'react-i18next'; - -import { appBoundClassNames as classNames } from '../../common/boundClassNames'; - -import courseShape from '../../shapes/model/subject/CourseShape'; - -const CourseSimpleBlock = ({ t, course }) => { - return ( -
-
- {course[t('js.property.title')]} -
-
{course.old_code}
-
- ); -}; - -CourseSimpleBlock.propTypes = { - course: courseShape.isRequired, -}; - -export default withTranslation()(React.memo(CourseSimpleBlock)); diff --git a/src/components/blocks/CourseSimpleBlock.tsx b/src/components/blocks/CourseSimpleBlock.tsx new file mode 100644 index 0000000..80a6107 --- /dev/null +++ b/src/components/blocks/CourseSimpleBlock.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { withTranslation } from 'react-i18next'; +import { appBoundClassNames as classNames } from '@/common/boundClassNames'; +import Course from '@/shapes/model/subject/Course'; +import { useTranslatedString } from '@/hooks/useTranslatedString'; + +interface Props { + course: Course; +} + +/** + * Component `CourseSimpleBlock` displays a brief overview of a course within the `CourseRelatedCoursesSubSection` on the `DictionaryPage`. + * It shows the title and code of the course. + */ +const CourseSimpleBlock: React.FC = ({ course }) => { + const translate = useTranslatedString(); + + return ( +
+
{translate(course, 'title')}
+
{course.old_code}
+
+ ); +}; + +export default withTranslation()(React.memo(CourseSimpleBlock)); diff --git a/src/components/blocks/PlannerCourseBlock.jsx b/src/components/blocks/PlannerCourseBlock.jsx deleted file mode 100644 index 4641e07..0000000 --- a/src/components/blocks/PlannerCourseBlock.jsx +++ /dev/null @@ -1,84 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { withTranslation } from 'react-i18next'; - -import { appBoundClassNames as classNames } from '../../common/boundClassNames'; - -import courseShape from '../../shapes/model/subject/CourseShape'; -import { arbitraryPseudoCourseShape } from '../../shapes/state/planner/ItemFocusShape'; - -const PlannerCourseBlock = ({ - t, - course, - isRaised, - isDimmed, - isAdded, - onMouseOver, - onMouseOut, - onClick, - addToPlanner, -}) => { - const handleMouseOver = onMouseOver - ? (event) => { - onMouseOver(course); - } - : null; - const handleMouseOut = onMouseOut - ? (event) => { - onMouseOut(course); - } - : null; - const handleClick = onClick - ? (event) => { - onClick(course); - } - : null; - const handleAddToPlannerClick = (event) => { - event.stopPropagation(); - addToPlanner(course); - }; - - return ( -
-
{t('ui.others.added')}
-
-
- {`${course.department[t('js.property.name')]} / ${course[t('js.property.type')]}`} -
-
- {course[t('js.property.title')]} -
-
{course.old_code}
-
- -
- ); -}; - -PlannerCourseBlock.propTypes = { - course: PropTypes.oneOfType([courseShape, arbitraryPseudoCourseShape]).isRequired, - isRaised: PropTypes.bool, - isDimmed: PropTypes.bool, - isAdded: PropTypes.bool.isRequired, - onMouseOver: PropTypes.func, - onMouseOut: PropTypes.func, - onClick: PropTypes.func, - addToPlanner: PropTypes.func, -}; - -export default withTranslation()(React.memo(PlannerCourseBlock)); diff --git a/src/components/blocks/PlannerCourseBlock.tsx b/src/components/blocks/PlannerCourseBlock.tsx new file mode 100644 index 0000000..c90af9e --- /dev/null +++ b/src/components/blocks/PlannerCourseBlock.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { withTranslation } from 'react-i18next'; +import { appBoundClassNames as classNames } from '@/common/boundClassNames'; +import Course from '@/shapes/model/subject/Course'; +import { ArbitraryPseudoCourse } from '@/shapes/state/planner/ItemFocus'; +import { useTranslatedString } from '@/hooks/useTranslatedString'; + +interface Props { + t: (string: string) => string; + course: Course | ArbitraryPseudoCourse; + isRaised?: boolean; + isDimmed?: boolean; + isAdded: boolean; + onMouseOver?: (course: Course | ArbitraryPseudoCourse) => void; + onMouseOut?: (course: Course | ArbitraryPseudoCourse) => void; + onClick?: (course: Course | ArbitraryPseudoCourse) => void; + addToPlanner: (course: Course | ArbitraryPseudoCourse) => void; +} + +/** + * Component `PlannerCourseBlock` displays an overview of a course within the search results on the `PlannerPage`. + * It shows the title, classification, and code of the course. + */ +const PlannerCourseBlock: React.FC = ({ + t, + course, + isRaised, + isDimmed, + isAdded, + onMouseOver, + onMouseOut, + onClick, + addToPlanner, +}) => { + const translate = useTranslatedString(); + + const handleAddToPlannerClick = (event: React.MouseEvent) => { + event.stopPropagation(); + addToPlanner(course); + }; + + return ( +
onClick?.(course)} + onMouseOver={() => onMouseOver?.(course)} + onMouseOut={() => onMouseOut?.(course)}> +
{t('ui.others.added')}
+
+
+ {`${course.department && translate(course.department, 'name')} / ${translate( + course, + 'type', + )}`} +
+
+ {translate(course, 'title')} +
+
{course.old_code}
+
+ +
+ ); +}; + +export default withTranslation()(React.memo(PlannerCourseBlock)); diff --git a/src/components/blocks/ProjectBlock.jsx b/src/components/blocks/ProjectBlock.jsx deleted file mode 100644 index ec1dbb4..0000000 --- a/src/components/blocks/ProjectBlock.jsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { withTranslation } from 'react-i18next'; - -import { appBoundClassNames as classNames } from '../../common/boundClassNames'; - -const ProjectBlock = ({ t, project, isRaised, onClick }) => { - const handleClick = onClick - ? (event) => { - onClick(project); - } - : null; - - return ( -
-
{project.mainTitle}
-
{project.subTitle}
-
{project.period}
-
- ); -}; - -ProjectBlock.propTypes = { - project: PropTypes.shape({ - index: PropTypes.number.isRequired, - mainTitle: PropTypes.string.isRequired, - subTitle: PropTypes.string.isRequired, - period: PropTypes.string.isRequired, - }).isRequired, - onClick: PropTypes.func, - isRaised: PropTypes.bool.isRequired, -}; - -export default withTranslation()(React.memo(ProjectBlock)); diff --git a/src/components/blocks/ProjectBlock.tsx b/src/components/blocks/ProjectBlock.tsx new file mode 100644 index 0000000..3ec6a2f --- /dev/null +++ b/src/components/blocks/ProjectBlock.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { withTranslation } from 'react-i18next'; +import { appBoundClassNames as classNames } from '@/common/boundClassNames'; + +interface Props { + project: { + index: number; + mainTitle: string; + subTitle: string; + period: string; + }; + onClick?: (project: { index: number }) => void; + isRaised: boolean; +} + +/** + * Component `ProjectBlock` displays an overview of a project on the `CreditPage`. + * It shows the title, subtitle, and period of the project. + */ +const ProjectBlock: React.FC = ({ project, isRaised, onClick }) => { + return ( +
onClick?.(project)}> +
{project.mainTitle}
+
{project.subTitle}
+
{project.period}
+
+ ); +}; + +export default withTranslation()(React.memo(ProjectBlock));