github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/public/src/components/group/GroupForm.tsx (about)

     1  import React, { useEffect, useState } from "react"
     2  import { Enrollment, Enrollment_UserStatus, Group } from "../../../proto/qf/types_pb"
     3  import { Color, getCourseID, hasTeacher, isApprovedGroup, isHidden, isPending, isStudent } from "../../Helpers"
     4  import { useActions, useAppState } from "../../overmind"
     5  import Button, { ButtonType } from "../admin/Button"
     6  import DynamicButton from "../DynamicButton"
     7  import Search from "../Search"
     8  
     9  
    10  const GroupForm = (): JSX.Element | null => {
    11      const state = useAppState()
    12      const actions = useActions()
    13  
    14      const [query, setQuery] = useState<string>("")
    15      const [enrollmentType, setEnrollmentType] = useState<Enrollment_UserStatus.STUDENT | Enrollment_UserStatus.TEACHER>(Enrollment_UserStatus.STUDENT)
    16      const courseID = getCourseID()
    17  
    18      const group = state.activeGroup
    19      useEffect(() => {
    20          if (isStudent(state.enrollmentsByCourseID[courseID.toString()])) {
    21              actions.setActiveGroup(new Group())
    22              actions.updateGroupUsers(state.self.clone())
    23          }
    24          return () => {
    25              actions.setActiveGroup(null)
    26          }
    27      }, [])
    28      if (!group) {
    29          return null
    30      }
    31      const userIds = group.users.map(user => user.ID)
    32  
    33      const search = (enrollment: Enrollment): boolean => {
    34          if (userIds.includes(enrollment.userID) || enrollment.group && enrollment.groupID !== group.ID) {
    35              return true
    36          }
    37          if (enrollment.user) {
    38              return isHidden(enrollment.user.Name, query)
    39          }
    40          return false
    41      }
    42  
    43      const enrollments = state.courseEnrollments[courseID.toString()].map(enrollment => enrollment.clone())
    44  
    45      // Determine the user's enrollment status (teacher or student)
    46      const isTeacher = hasTeacher(state.status[courseID.toString()])
    47  
    48      const enrollmentFilter = (enrollment: Enrollment) => {
    49          if (isTeacher) {
    50              // If the user is a teacher, show all enrollments of the selected enrollment type
    51              return enrollment.status === enrollmentType
    52          }
    53          // Show all students
    54          return enrollment.status === Enrollment_UserStatus.STUDENT
    55      }
    56  
    57      const groupFilter = (enrollment: Enrollment) => {
    58          if (group && group.ID) {
    59              // If a group is being edited, show users that are in the group
    60              // This is to allow users to be removed from the group, and to be re-added
    61              return enrollment.groupID === group.ID || enrollment.groupID === BigInt(0)
    62          }
    63          // Otherwise, show users that are not in a group
    64          return enrollment.groupID === BigInt(0)
    65      }
    66  
    67      const sortedAndFilteredEnrollments = enrollments
    68          // Filter enrollments where the user is not a student (or teacher), or the user is already in a group
    69          .filter(enrollment => enrollmentFilter(enrollment) && groupFilter(enrollment))
    70          // Sort by name
    71          .sort((a, b) => (a.user?.Name ?? "").localeCompare((b.user?.Name ?? "")))
    72  
    73      const AvailableUser = ({ enrollment }: { enrollment: Enrollment }) => {
    74          const id = enrollment.userID
    75          if (isPending(enrollment)) {
    76              return null
    77          }
    78          if (id !== state.self.ID && !userIds.includes(id)) {
    79              return (
    80                  <li hidden={search(enrollment)} key={id.toString()} className="list-group-item">
    81                      {enrollment.user?.Name}
    82                      <Button
    83                          text={"+"}
    84                          color={Color.GREEN}
    85                          type={ButtonType.BADGE}
    86                          className="ml-2 float-right"
    87                          onClick={() => actions.updateGroupUsers(enrollment.user)}
    88                      />
    89                  </li>
    90              )
    91          }
    92          return null
    93      }
    94  
    95      const groupMembers = group.users.map(user => {
    96          return (
    97              <li key={user.ID.toString()} className="list-group-item">
    98                  <img id="group-image" src={user.AvatarURL} alt="" />
    99                  {user.Name}
   100                  <Button
   101                      text={"-"}
   102                      color={Color.RED}
   103                      type={ButtonType.BADGE}
   104                      className="float-right"
   105                      onClick={() => actions.updateGroupUsers(user)}
   106                  />
   107              </li>
   108          )
   109      })
   110  
   111      const toggleEnrollmentType = () => {
   112          if (hasTeacher(enrollmentType)) {
   113              setEnrollmentType(Enrollment_UserStatus.STUDENT)
   114          } else {
   115              setEnrollmentType(Enrollment_UserStatus.TEACHER)
   116          }
   117      }
   118  
   119      const EnrollmentTypeButton = () => {
   120          if (!isTeacher) {
   121              return <div>Students</div>
   122          }
   123          return (
   124              <button className="btn btn-primary w-100" type="button" onClick={toggleEnrollmentType}>
   125                  {enrollmentType === Enrollment_UserStatus.STUDENT ? "Students" : "Teachers"}
   126              </button>
   127          )
   128      }
   129  
   130      const GroupNameBanner = <div className="card-header" style={{ textAlign: "center" }}>{group.name}</div>
   131      const GroupNameInput = group && isApprovedGroup(group)
   132          ? null
   133          : <input placeholder={"Group Name:"} onKeyUp={e => actions.updateGroupName(e.currentTarget.value)} />
   134  
   135      return (
   136          <div className="container">
   137              <div className="row">
   138                  <div className="card well col-md-offset-2">
   139                      <div className="card-header" style={{ textAlign: "center" }}>
   140                          <EnrollmentTypeButton />
   141                      </div>
   142                      <Search placeholder={"Search"} setQuery={setQuery} />
   143  
   144                      <ul className="list-group list-group-flush">
   145                          {sortedAndFilteredEnrollments.map((enrollment, index) => {
   146                              return <AvailableUser key={index} enrollment={enrollment} />
   147                          })}
   148                      </ul>
   149                  </div>
   150  
   151                  <div className='col'>
   152                      <div className="card well col-md-offset-2" >
   153                          {GroupNameBanner}
   154                          {GroupNameInput}
   155                          {groupMembers}
   156                          {group && group.ID ?
   157                              <div className="row justify-content-md-center">
   158                                  <DynamicButton
   159                                      text={"Update"}
   160                                      color={Color.BLUE}
   161                                      type={ButtonType.BUTTON}
   162                                      className="ml-2"
   163                                      onClick={() => actions.updateGroup(group)}
   164                                  />
   165                                  <Button
   166                                      text={"Cancel"}
   167                                      color={Color.RED}
   168                                      type={ButtonType.OUTLINE}
   169                                      className="ml-2"
   170                                      onClick={() => actions.setActiveGroup(null)}
   171                                  />
   172                              </div>
   173                              :
   174                              <DynamicButton
   175                                  text={"Create Group"}
   176                                  color={Color.GREEN}
   177                                  type={ButtonType.BUTTON}
   178                                  onClick={() => actions.createGroup({ courseID, users: userIds, name: group.name })}
   179                              />
   180                          }
   181                      </div>
   182                  </div>
   183              </div>
   184          </div >
   185      )
   186  }
   187  
   188  export default GroupForm