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

     1  import React from "react"
     2  import { Group, Group_GroupStatus } from "../../proto/qf/types_pb"
     3  import { Color, getCourseID, hasEnrollments, isApprovedGroup, isPendingGroup } from "../Helpers"
     4  import { useActions, useAppState } from "../overmind"
     5  import Button, { ButtonType } from "./admin/Button"
     6  import DynamicButton from "./DynamicButton"
     7  import GroupForm from "./group/GroupForm"
     8  import Search from "./Search"
     9  
    10  
    11  /* Lists all groups for a given course. */
    12  const Groups = (): JSX.Element => {
    13      const state = useAppState()
    14      const actions = useActions()
    15      const courseID = getCourseID()
    16  
    17      const groupSearch = (group: Group) => {
    18          // Show all groups if query is empty
    19          if (state.query.length === 0) {
    20              return false
    21          }
    22  
    23          // Show group if group name includes query
    24          if (group.name.toLowerCase().includes(state.query)) {
    25              return false
    26          }
    27  
    28          // Show group if any group user includes query
    29          for (const user of group.users) {
    30              if (user.Name.toLowerCase().includes(state.query)) {
    31                  return false
    32              }
    33          }
    34          // Hide group if none of the above include query
    35          return true
    36      }
    37  
    38      const GroupButtons = ({ group }: { group: Group }) => {
    39          const buttons: JSX.Element[] = []
    40          if (isPendingGroup(group)) {
    41              buttons.push(
    42                  <DynamicButton
    43                      text={"Approve"}
    44                      color={Color.BLUE}
    45                      type={ButtonType.BADGE}
    46                      onClick={() => actions.updateGroupStatus({ group, status: Group_GroupStatus.APPROVED })}
    47                  />
    48              )
    49          }
    50          buttons.push(
    51              <Button
    52                  text={"Edit"}
    53                  color={Color.YELLOW}
    54                  type={ButtonType.BADGE}
    55                  className="ml-2"
    56                  onClick={() => actions.setActiveGroup(group)}
    57              />
    58          )
    59          buttons.push(
    60              <DynamicButton
    61                  text={"Delete"}
    62                  color={Color.RED}
    63                  type={ButtonType.BADGE}
    64                  className="ml-2"
    65                  onClick={() => actions.deleteGroup(group)}
    66              />
    67          )
    68  
    69          return <td className="d-flex">{buttons}</td>
    70      }
    71  
    72      const GroupMembers = ({ group }: { group: Group }) => {
    73          if (!hasEnrollments(group)) {
    74              return <td>No members</td>
    75          }
    76  
    77          const members = group.enrollments.map((enrollment, index) => {
    78              return (
    79                  <span key={enrollment.ID.toString()} className="inline-block">
    80                      <a href={`https://github.com/${enrollment.user?.Login}`} target="_blank" rel="noopener noreferrer">{enrollment.user?.Name}</a>
    81                      {index >= group.enrollments.length - 1 ? "" : ", "}
    82                  </span>
    83              )
    84          })
    85          return <td>{members}</td>
    86      }
    87  
    88      const GroupRow = ({ group }: { group: Group }) => {
    89          return (
    90              <tr hidden={groupSearch(group)}>
    91                  <td key={group.ID.toString()}>
    92                      {group.name}
    93                      <span className="badge badge-warning ml-2">{isPendingGroup(group) ? "Pending" : null}</span>
    94                  </td>
    95                  <GroupMembers group={group} />
    96                  <GroupButtons group={group} />
    97              </tr>
    98          )
    99      }
   100  
   101      // Generates JSX.Element array containing all groups for the course
   102      const PendingGroups = state.groups[courseID.toString()]?.filter(group => isPendingGroup(group)).map(group => {
   103          return <GroupRow key={group.ID.toString()} group={group} />
   104      })
   105  
   106      const ApprovedGroups = state.groups[courseID.toString()]?.filter(group => isApprovedGroup(group)).map(group => {
   107          return <GroupRow key={group.ID.toString()} group={group} />
   108      })
   109  
   110      // If a group is active (being edited), show the group form
   111      if (state.activeGroup) {
   112          return <GroupForm />
   113      }
   114  
   115      return (
   116          <div className="box">
   117              <div className="pb-2">
   118                  <Search />
   119              </div>
   120              <table className="table table-striped table-grp table-hover">
   121                  <thead className="thead-dark">
   122                      <th>Name</th>
   123                      <th>Members</th>
   124                      <th>Manage</th>
   125                  </thead>
   126                  <tbody>
   127                      {PendingGroups}
   128                      {ApprovedGroups}
   129                  </tbody>
   130              </table>
   131          </div>
   132      )
   133  }
   134  
   135  export default Groups