diff --git a/src/SelectCourses.tsx b/src/SelectCourses.tsx new file mode 100644 index 0000000..b81ba38 --- /dev/null +++ b/src/SelectCourses.tsx @@ -0,0 +1,173 @@ +import Course from "./Course"; +import CourseGroup from "./CourseGroup"; +import SelectSubjectRequirement from "./SelectSubjectRequirement"; +import { checkSelect, countUnitFromCode } from "./checkSelect"; +import { GradRequirement } from "./data/gradRequirement"; + +const Select = ({ + courseList, + includeCourseYear, + gradRequirement, +}: { + courseList: Course[]; + includeCourseYear: boolean; + gradRequirement: GradRequirement; +}) => { + const { selectList, sumUnit, courseGroupList, courseIDList, groupUnitList } = + checkSelect(courseList, gradRequirement); + + return ( +
+

選択科目の条件一覧

+ + + +
+ ); +}; + +const SelectGroups = ({ + selectList, + courseIDList, + courseList, + includeCourseYear, +}: { + selectList: SelectSubjectRequirement[]; + courseIDList: string[]; + courseList: Course[]; + includeCourseYear: boolean; +}) => ( + <> + {selectList.map((selectSubject) => ( + + ))} + +); + +const SelectGroup = ({ + selectSubject, + courseIDList, + courseList, + includeCourseYear, +}: { + selectSubject: SelectSubjectRequirement; + courseIDList: string[]; + courseList: Course[]; + includeCourseYear: boolean; +}) => { + const { excludeCourseList, unitCount } = countUnitFromCode( + selectSubject, + courseIDList, + courseList + ); + + return ( +
+ + {selectSubject.message}{" "} + {unitCount >= selectSubject.minimum ? : }{" "} + {unitCount}({selectSubject.minimum}-{selectSubject.maximum}){" "} + {unitCount > selectSubject.maximum && (上限を超えています)} + + +
+ ); +}; + +const DetailContent = ({ + courses, + includeCourseYear, +}: { + courses: Course[]; + includeCourseYear: boolean; +}) => ( + <> + {courses.map((course) => { + const year = includeCourseYear ? `(${course.year}年度)` : ""; + return ( +
  • + {course.id} {course.name} {year}: {course.grade} +
  • + ); + })} + +); + +const CourseGroups = ({ + courseGroupList, + groupUnitList, +}: { + courseGroupList: CourseGroup[]; + groupUnitList: { [key: number]: number }; +}) => ( + <> + {courseGroupList.map((courseGroup, index) => ( + + ))} + +); + +const CourseGroupFC = ({ + courseGroup, + groupUnitList, + index, +}: { + courseGroup: CourseGroup; + groupUnitList: { [key: number]: number }; + index: number; +}) => { + const courseGroupUnit = groupUnitList[courseGroup.id]; + const marubatsu = + courseGroupUnit >= courseGroup.minUnit ? : ; + const exceedMessage = + courseGroupUnit > courseGroup.maxUnit ? ( + (単位上限を超えています) + ) : ( + "" + ); + return ( + <> +

    {courseGroup.name}

    + {groupUnitList[index]}/({courseGroup.minUnit}~{courseGroup.maxUnit}) + {marubatsu} + {exceedMessage} + + ); +}; + +const SelectCourseTotal = ({ + sumUnit, + selectMinimumUnit, +}: { + sumUnit: number; + selectMinimumUnit: number; +}) => ( +

    + 合計{sumUnit}/{selectMinimumUnit}単位 +

    +); + +export { Select }; diff --git a/src/SelectSubjectRequirement.ts b/src/SelectSubjectRequirement.ts index f8bafbc..a39890d 100644 --- a/src/SelectSubjectRequirement.ts +++ b/src/SelectSubjectRequirement.ts @@ -5,7 +5,7 @@ class SelectSubjectRequirement { public maximum: number, public isExcludeRequirement: boolean, public message: string, - public group: number + public group: 0 | 1 | 2 | 3 ) {} } diff --git a/src/checkCompulsory.ts b/src/checkCompulsory.ts index 2f5fe31..7b8b925 100644 --- a/src/checkCompulsory.ts +++ b/src/checkCompulsory.ts @@ -93,8 +93,8 @@ const checkCourseCertificate = (courseList: Course[]): Course[] => { "English Presentation Skills II": "31L", }; - courseList.map((c) => { - if (c.grade == "認" && compulsoryEnglishDict[c.name] != undefined) { + courseList.forEach((c) => { + if (c.grade === "認" && compulsoryEnglishDict[c.name] !== undefined) { c.id = compulsoryEnglishDict[c.name]; } }); @@ -133,7 +133,7 @@ const checkCompulsory = ( let courseExists: boolean; let alternativeExists: boolean; - compulsoryList.map((compulsory) => { + compulsoryList.forEach((compulsory) => { //初期化 detectedCourses = []; courseName = compulsory; @@ -160,7 +160,7 @@ const checkCompulsory = ( (courseID) => beginWithMatch(courseID, codes) && !beginWithMatch(courseID, except) ) - .map((courseID) => { + .forEach((courseID) => { const unit = getCourseUnitFromID(courseID, courseList); detectedCourses.push(searchCourseFromID(courseID, courseList)); excludeCourseList.push(searchCourseFromID(courseID, courseList)); diff --git a/src/checkSelect.ts b/src/checkSelect.ts index 9402f78..6fd96b5 100644 --- a/src/checkSelect.ts +++ b/src/checkSelect.ts @@ -1,6 +1,7 @@ import Course from "./Course"; import CourseGroup from "./CourseGroup"; import codeType from "./data/courseCodeTypes"; +import { GradRequirement } from "./data/gradRequirement"; class SelectSubjectRequirement { constructor( @@ -9,7 +10,7 @@ class SelectSubjectRequirement { public maximum: number, public isExcludeRequirement: boolean, public message: string, - public group: number + public group: 0 | 1 | 2 | 3 ) {} } @@ -46,7 +47,7 @@ const countUnitFromCode = ( selectSubject: SelectSubjectRequirement, courseIDList: string[], courseList: Course[] -): [Course[], number] => { +): { excludeCourseList: Course[]; unitCount: number } => { const isExclusive = selectSubject.isExcludeRequirement; const codes = selectSubject.codes; let unitCount = 0; @@ -57,7 +58,7 @@ const countUnitFromCode = ( // 通常の条件のカウントの場合 if (!isExclusive) { - codes.map((code) => { + codes.forEach((code) => { // 科目のタグ表記ではない場合 if (!code.startsWith("*")) { includedIDList = courseIDList.filter((id) => id.startsWith(code)); @@ -71,7 +72,7 @@ const countUnitFromCode = ( let tag = code.replace("*", ""); tagCodes = codeType[tag as keyof typeof codeType].codes; tagExcept = codeType[tag as keyof typeof codeType].except; - tagCodes.map((tagCode) => { + tagCodes.forEach((tagCode) => { includedIDList = courseIDList.filter( (id) => id.startsWith(tagCode) && !beginWithMatch(id, tagExcept) ); @@ -86,12 +87,12 @@ const countUnitFromCode = ( //除外するカウントの場合 let expandedExcludeList: string[] = []; let expandedExceptList: string[] = []; - codes.map((code) => { + codes.forEach((code) => { if (!code.startsWith("*")) { expandedExcludeList.push(code); } else { //タグを展開してリストに追加 - let tag = code.replace("*", ""); + const tag = code.replace("*", ""); tagCodes = codeType[tag as keyof typeof codeType].codes; tagExcept = codeType[tag as keyof typeof codeType].except; expandedExcludeList = expandedExcludeList.concat(tagCodes); @@ -108,126 +109,71 @@ const countUnitFromCode = ( ); unitCount += getUnitFromIDList(includedIDList, courseList); } - return [excludeCourseList, unitCount]; + return { excludeCourseList, unitCount }; }; -const createDetail = ( - detectedCourses: Course[], - includeCourseYear: boolean -): string[] => - detectedCourses.map((course) => { - const year = includeCourseYear ? `(${course.year}年度)` : ""; - console.log(`${course.id} ${course.name} ${year}: ${course.grade}
    `); - return `${course.id} ${course.name} ${year}: ${course.grade}
    `; - }); - const checkSelect = ( courseList: Course[], - includeCourseYear: boolean, - requirementObject: any -): { newCourseList: Course[]; sumUnit: number } => { - const selectList: SelectSubjectRequirement[] = - requirementObject.courses.select.map( - (x: any) => - new SelectSubjectRequirement(x[0], x[1], x[2], x[3], x[4], x[5]) - ); - const courseIDList: string[] = createElementList("id", courseList); - const courseGroupList: CourseGroup[] = requirementObject.courses.groups.map( - (x: any) => new CourseGroup(x[0], x[1], x[2], x[3]) + requirementObject: GradRequirement +) => { + const selectList = requirementObject.courses.select.map( + (x) => new SelectSubjectRequirement(x[0], x[1], x[2], x[3], x[4], x[5]) + ); + const courseIDList = createElementList("id", courseList); + const courseGroupList = requirementObject.courses.groups.map( + (x) => new CourseGroup(x[0], x[1], x[2], x[3]) ); - let groupUnitList: { [key: number]: number } = { 0: 0, 1: 0, 2: 0, 3: 0 }; - let groupid: number; - let excludeCourseList: Course[] = []; - let detectedCourses: Course[]; - let tmp: [Course[], number]; - let resultArray: string[] = []; - let unitCount; - let sumUnit = 0; - let isCompleted = true; - - selectList.map((selectSubject) => { - groupid = selectSubject.group; - tmp = countUnitFromCode(selectSubject, courseIDList, courseList); - unitCount = tmp[1]; - groupUnitList[groupid] += unitCount; - detectedCourses = tmp[0]; - excludeCourseList = excludeCourseList.concat(detectedCourses); - if (unitCount >= selectSubject.maximum) { - resultArray.push(`
    - - ${selectSubject.message} ${unitCount}(${ - selectSubject.minimum - }~${selectSubject.maximum}) (上限を超えています) - - ${createDetail(detectedCourses, includeCourseYear).map( - (course) => `
  • ${course}
  • ` - )} -
    `); - } else if (unitCount >= selectSubject.minimum) { - resultArray.push( - `
    - - ${selectSubject.message} ${unitCount}(${ - selectSubject.minimum - }~${selectSubject.maximum}) - - ${createDetail(detectedCourses, includeCourseYear).join("")} -
    ` - ); - } else { - // 条件を満たしていない場合 - resultArray.push( - "
    " + - selectSubject.message + - " " + - "" + - " " + - unitCount + - "(" + - String(selectSubject.minimum) + - "~" + - String(selectSubject.maximum) + - ")" + - "" + - createDetail(detectedCourses, includeCourseYear).join("") + - "
    " - ); - } - }); - console.log(resultArray); - resultArray.unshift("

    選択科目の条件一覧

    "); - let courseGroupUnit: number; - let marubatsu: string; - let exceedMessage: string; - courseGroupList.map((courseGroup, index) => { - exceedMessage = ""; - courseGroupUnit = groupUnitList[courseGroup.id]; - if (courseGroupUnit >= courseGroup.minUnit) { - marubatsu = ""; - if (courseGroupUnit >= courseGroup.maxUnit) { - exceedMessage = " (単位上限を超えています)"; - } - } else { - marubatsu = ""; - isCompleted = false; - } - resultArray.push(`

    ${courseGroup.name}

    - ${groupUnitList[index]}/(${courseGroup.minUnit}~${courseGroup.maxUnit})${marubatsu}${exceedMessage}`); - sumUnit += Math.min(courseGroupUnit, courseGroup.maxUnit); - }); - sumUnit = Math.min(sumUnit, requirementObject.courses.selectMinimumUnit); + const { groupUnitList, excludeCourseList } = selectList.reduce<{ + groupUnitList: { [key: number]: number }; + excludeCourseList: Course[]; + }>( + (acc, selectSubject) => { + const { excludeCourseList: _excludeCourseList, unitCount: _unitCount } = + countUnitFromCode(selectSubject, courseIDList, courseList); + const groupId = selectSubject.group; + acc.groupUnitList[groupId] += _unitCount; + acc.excludeCourseList = acc.excludeCourseList.concat(_excludeCourseList); + return acc; + }, + { groupUnitList: { 0: 0, 1: 0, 2: 0, 3: 0 }, excludeCourseList: [] } + ); - resultArray.push(`

    - 合計${sumUnit}/${requirementObject.courses.selectMinimumUnit}単位 -

    `); - document.getElementById("select")!.innerHTML = resultArray.join("
    "); + const sumUnit = calcSumUnit({ + courseGroupList, + groupUnitList, + selectMinimumUnit: requirementObject.courses.selectMinimumUnit, + }); // 差集合をとる const newCourseList = courseList.filter( (val) => !excludeCourseList.includes(val) ); - return { newCourseList: newCourseList, sumUnit: sumUnit }; + + return { + newCourseList, + sumUnit, + selectList, + courseGroupList, + courseIDList, + groupUnitList, + }; +}; + +const calcSumUnit = ({ + courseGroupList, + groupUnitList, + selectMinimumUnit, +}: { + courseGroupList: CourseGroup[]; + groupUnitList: { [key: number]: number }; + selectMinimumUnit: number; +}) => { + const _sumUnit = courseGroupList.reduce((acc, courseGroup) => { + const courseGroupUnit = groupUnitList[courseGroup.id]; + return acc + Math.min(courseGroupUnit, courseGroup.maxUnit); + }, 0); + return Math.min(_sumUnit, selectMinimumUnit); }; -export default checkSelect; +export { checkSelect, countUnitFromCode }; diff --git a/src/data/gradRequirement.ts b/src/data/gradRequirement.ts index 140bfdf..74ea6f7 100644 --- a/src/data/gradRequirement.ts +++ b/src/data/gradRequirement.ts @@ -16,7 +16,7 @@ const gradRequirement = z.object({ z.number(), z.boolean(), z.string(), - z.number(), + z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]), ]) ), selectMinimumUnit: z.number(), diff --git a/src/graduationChecker.tsx b/src/graduationChecker.tsx index 4f12926..b8892bd 100644 --- a/src/graduationChecker.tsx +++ b/src/graduationChecker.tsx @@ -2,7 +2,7 @@ import React from "react"; import Course from "./Course"; import { gradRequirement, GradRequirement } from "./data/gradRequirement"; import checkCompulsory from "./checkCompulsory"; -import checkSelect from "./checkSelect"; +import { checkSelect } from "./checkSelect"; import showRequirements from "./showRequirements"; import "./graduationChecker.css"; import { GradePieChart } from "./GradePieChart"; @@ -15,6 +15,7 @@ import klis_ksc22 from "./data/klis_ksc22.json"; import klis_kis22 from "./data/klis_kis22.json"; import klis_irm22 from "./data/klis_irm22.json"; import { TotalGPA } from "./totalGPA"; +import { Select } from "./SelectCourses"; const getRequirement = (major: Major): GradRequirement => gradRequirement.parse( @@ -35,16 +36,21 @@ const GraduationChecker: React.FC = () => { const [exceptCourses, setExceptCourses] = React.useState( null ); + const [includeCourseYear, setIncludeCourseYear] = + React.useState(false); const [usageVisible, setUsageVisible] = React.useState(true); const [sumUnit, setSumUnit] = React.useState(0); const [minimumGraduationUnit, setMinimumGraduationUnit] = React.useState(124); const [isCompulsoryCompleted, setIsCompulsoryCompleted] = React.useState(false); + const [gradRequirement, setGradRequirement] = React.useState( + getRequirement("mast21") + ); - const includeCourseYear = React.useRef(null); const majorSelect = React.useRef(null); const enrollYear = React.useRef(null); + const loadCSV = (csv: string): Course[] => { document.getElementById("result")!.style.display = "block"; csv = csv.replaceAll('"', ""); @@ -76,16 +82,13 @@ const GraduationChecker: React.FC = () => { // 使い方の表示を消す setUsageVisible(false); //チェックボックスの判定 - const checkBox = includeCourseYear.current; const tmpMajor = majorSelect.current!.value + enrollYear.current!.value; const major = (tmpMajor as Major) || "mast21"; - const requirementObject = getRequirement(major); + setGradRequirement(getRequirement(major)); - const isChecked = (checkBox && checkBox.checked) || false; const reader = new FileReader(); const minimumGraduationUnit = 124; - const compulsoryRequirementUnit = - requirementObject.courses.compulsorySumUnit; + const compulsoryRequirementUnit = gradRequirement.courses.compulsorySumUnit; let sumUnit = 0; let isCompulsoryCompleted = false; reader.readAsText(csv); @@ -95,12 +98,12 @@ const GraduationChecker: React.FC = () => { const { newCourseList: compulsoryCourseList, sumUnit: compulsorySumUnit, - } = checkCompulsory(courseList, isChecked, requirementObject); + } = checkCompulsory(courseList, includeCourseYear, gradRequirement); isCompulsoryCompleted = compulsorySumUnit === compulsoryRequirementUnit; const { newCourseList: selectCourseList, sumUnit: selectSumUnit } = - checkSelect(compulsoryCourseList, isChecked, requirementObject); + checkSelect(compulsoryCourseList, gradRequirement); sumUnit = selectSumUnit + compulsorySumUnit; setSumUnit(sumUnit); setMinimumGraduationUnit(minimumGraduationUnit); @@ -146,7 +149,7 @@ const GraduationChecker: React.FC = () => { id="includeCourseYear" type="checkbox" name="includeCourseYear" - ref={includeCourseYear} + onChange={(e) => setIncludeCourseYear(e.target.checked)} />

    @@ -182,7 +185,13 @@ const GraduationChecker: React.FC = () => {


    -
    + {courseList && ( + + )}

    卒業要件外の科目

    {exceptCourses ? ( @@ -286,9 +295,9 @@ const Sum = ({

    {sumUnit >= minimumGraduationUnit && isCompulsoryCompleted ? ( - + ) : ( - + )}

    {sumUnit >= minimumGraduationUnit && !isCompulsoryCompleted && (