github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/public/src/components/forms/CourseForm.tsx (about) 1 import React, { useState } from "react" 2 import { useActions } from "../../overmind" 3 import { Course } from "../../../proto/qf/types_pb" 4 import { Organization } from "../../../proto/qf/requests_pb" 5 import FormInput from "./FormInput" 6 import CourseCreationInfo from "../admin/CourseCreationInfo" 7 import { useHistory } from "react-router" 8 import { defaultTag, defaultYear } from "../../Helpers" 9 10 11 // TODO: There are currently issues with navigating a new course without refreshing the page to trigger a state reload. 12 // TODO: Plenty of required fields are undefined across multiple components. 13 14 /** CourseForm is used to create a new course or edit an existing course. 15 * If `editCourse` is provided, the existing course will be modified. 16 * If no course is provided, a new course will be created. */ 17 const CourseForm = ({ editCourse }: { editCourse?: Course }): JSX.Element | null => { 18 const actions = useActions() 19 const history = useHistory() 20 21 // TODO: This could go in go in a course-specific Overmind namespace rather than local state. 22 // Local state for organization name to be checked against the server 23 const [orgName, setOrgName] = useState("") 24 const [org, setOrg] = useState<Organization>() 25 26 // Local state containing the course to be created or edited (if any) 27 const [course, setCourse] = useState(editCourse ? editCourse.clone() : new Course) 28 29 // Local state containing a boolean indicating whether the organization is valid. Courses that are being edited do not need to be validated. 30 const [orgFound, setOrgFound] = useState<boolean>(editCourse ? true : false) 31 32 /* Date object used to fill in certain default values for new courses */ 33 const date = new Date(Date.now()) 34 if (!editCourse) { 35 course.year = defaultYear(date) 36 course.tag = defaultTag(date) 37 } 38 39 const handleChange = (event: React.FormEvent<HTMLInputElement>) => { 40 const { name, value } = event.currentTarget 41 switch (name) { 42 case "courseName": 43 course.name = value 44 break 45 case "courseTag": 46 course.tag = value 47 break 48 case "courseCode": 49 course.code = value 50 break 51 case "courseYear": 52 course.year = Number(value) 53 break 54 case "slipDays": 55 course.slipDays = Number(value) 56 break 57 } 58 setCourse(course) 59 } 60 61 // Creates a new course if no course is being edited, otherwise updates the existing course 62 const submitHandler = async (e: React.FormEvent<HTMLFormElement>) => { 63 e.preventDefault() 64 if (editCourse) { 65 actions.editCourse({ course }) 66 } else { 67 if (org) { 68 const success = await actions.createCourse({ course, org }) 69 // If course creation was successful, redirect to the course page 70 if (success) { 71 history.push("/courses") 72 } 73 } else { 74 //org not found 75 } 76 } 77 } 78 79 // Trigger grpc call to check if org exists 80 const getOrganization = async () => { 81 const org = (await actions.getOrganization(orgName)).message 82 if (org) { 83 setOrg(org) 84 setOrgFound(true) 85 } else { 86 setOrgFound(false) 87 } 88 } 89 90 return ( 91 <div className="container"> 92 {editCourse ? null : <CourseCreationInfo />} 93 <div className="row" hidden={editCourse ? true : false}> 94 <div className="col input-group mb-3"> 95 <div className="input-group-prepend"> 96 <div className="input-group-text">Organization</div> 97 </div> 98 <input className="form-control" disabled={orgFound ? true : false} onKeyUp={e => setOrgName(e.currentTarget.value)} /> 99 <span className={orgFound ? "btn btn-success disabled" : "btn btn-primary"} onClick={!orgFound ? () => getOrganization() : () => { return }}> 100 {orgFound ? <i className="fa fa-check" /> : "Find"} 101 </span> 102 </div> 103 </div> 104 {orgFound && 105 <form className="form-group" onSubmit={async e => await submitHandler(e)}> 106 <div className="row"> 107 <FormInput prepend="Name" 108 name="courseName" 109 placeholder={"Course Name"} 110 defaultValue={editCourse?.name} 111 onChange={handleChange} 112 /> 113 </div> 114 <div className="row"> 115 <FormInput 116 prepend="Code" 117 name="courseCode" 118 placeholder={"(ex. DAT320)"} 119 defaultValue={editCourse?.code} 120 onChange={handleChange} 121 /> 122 <FormInput 123 prepend="Tag" 124 name="courseTag" 125 placeholder={"(ex. Fall / Spring)"} 126 defaultValue={editCourse ? editCourse.tag : defaultTag(date)} 127 onChange={handleChange} 128 /> 129 </div> 130 <div className="row"> 131 <FormInput 132 prepend="Slip days" 133 name="slipDays" 134 placeholder={"(ex. 7)"} 135 defaultValue={editCourse?.slipDays.toString()} 136 onChange={handleChange} 137 type="number" 138 /> 139 <FormInput 140 prepend="Year" 141 name="courseYear" 142 placeholder={"(ex. 2021)"} 143 defaultValue={editCourse ? editCourse.year.toString() : defaultYear(date).toString()} 144 onChange={handleChange} 145 type="number" 146 /> 147 </div> 148 <input className="btn btn-primary" type="submit" value={editCourse ? "Edit Course" : "Create Course"} /> 149 </form> 150 } 151 </div> 152 ) 153 } 154 155 export default CourseForm