function LMSGrader({
  children,
  clientRPC,
  assignmentName,
  courseName,
  students: unorderedStudents,
}) {
  const {
    api: { authToken, sync: syncAPICallInfo },
  } = useContext(Config);

  // No initial current student selected
  const [currentStudentIndex, setCurrentStudentIndex] = useState(-1);

  // Students sorted by displayName
  const students = useMemo(() => {
    function compareNames(name1 = '', name2 = '') {
      if (name1.toLowerCase() < name2.toLowerCase()) {
        return -1;
      } else if (name1.toLowerCase() > name2.toLowerCase()) {
        return 1;
      } else {
        return 0;
      }
    }
    // Make a copy
    const students_ = [...unorderedStudents];

    students_.sort((student1, student2) => {
      return compareNames(student1.displayName, student2.displayName);
    });
    return students_;
  }, [unorderedStudents]);

  /**
   * Makes an RPC call to the sidebar to change to the focused user.
   */
  const changeFocusedUser = useCallback(
    /** @param {StudentInfo|null} user - The user to focus on in the sidebar */
    async user => {
      let groups = null;
      if (syncAPICallInfo && user?.lmsId) {
        // Request and set a list of groups specific to the student being graded
        const studentGroupsCallData = {
          ...syncAPICallInfo.data,
          gradingStudentId: user.lmsId,
        };
        try {
          groups = await apiCall({
            authToken,
            path: syncAPICallInfo.path,
            data: studentGroupsCallData,
          });
        } catch (e) {
          // TODO: Improved error handling
          console.error('Unable to fetch student groups from sync API');
        }
      }
      clientRPC.setFocusedUser(user, groups);
    },
    [authToken, clientRPC, syncAPICallInfo]
  );

  useEffect(() => {
    if (currentStudentIndex >= 0) {
      changeFocusedUser(students[currentStudentIndex]);
    } else {
      changeFocusedUser(null);
    }
  }, [students, changeFocusedUser, currentStudentIndex]);

  /**
   * Callback to set the current selected student.
   *
   * @param {number} studentIndex
   */
  const onSelectStudent = studentIndex => {
    setCurrentStudentIndex(studentIndex);
  };

  /**
   * Return the current student, or an empty object if there is none
   */
  const getCurrentStudent = () => {
    return currentStudentIndex >= 0 ? students[currentStudentIndex] : null;
  };

  return (
    <>
      <header
        className={classnames(
          'grid grid-cols-1 items-center gap-y-2 p-2',
          'lg:grid-cols-3 lg:gap-x-4 lg:px-3'
        )}
      >
        <div>
          <h1 className="text-lg p-0 m-0" data-testid="assignment-name">
            {assignmentName}
          </h1>
          <h2
            className="text-base font-medium p-0 m-0"
            data-testid="course-name"
          >
            {courseName}
          </h2>
        </div>

        <div
          className={classnames(
            'flex flex-col gap-2',
            'sm:flex-row',
            'lg:col-span-2 lg:gap-4 ' /* cols 2-3 of 3 */
          )}
        >
          <div className="flex-grow-0 sm:flex-grow">
            <StudentSelector
              onSelectStudent={onSelectStudent}
              students={students}
              selectedStudentIndex={currentStudentIndex}
            />
          </div>
          <div className="flex-grow sm:flex-grow-0">
            <SubmitGradeForm student={getCurrentStudent()} />
          </div>
        </div>
      </header>
      {children}
    </>
  );
}
/**
 * The LMSGrader component is fixed at the top of the page. This toolbar shows which assignment is currently
 * active as well as a list of students to both view and submit grades for.
 *
 * @param {LMSGraderProps} props
 */
The LMSGrader component is fixed at the top of the page. This toolbar shows which assignment is currently
active as well as a list of students to both view and submit grades for.


The LMSGrader component is fixed at the top of the page . This toolbar shows which assignment is currently active as well as a list of students to both view and submit grades for .
function LMSGrader({
  children,
  clientRPC,
  assignmentName,
  courseName,
  students: unorderedStudents,
}) {
  const {
    api: { authToken, sync: syncAPICallInfo },
  } = useContext(Config);
  const [currentStudentIndex, setCurrentStudentIndex] = useState(-1);
  const students = useMemo(() => {
    function compareNames(name1 = '', name2 = '') {
      if (name1.toLowerCase() < name2.toLowerCase()) {
        return -1;
      } else if (name1.toLowerCase() > name2.toLowerCase()) {
        return 1;
      } else {
        return 0;
      }
    }
    const students_ = [...unorderedStudents];
    students_.sort((student1, student2) => {
      return compareNames(student1.displayName, student2.displayName);
    });
    return students_;
  }, [unorderedStudents]);
  const changeFocusedUser = useCallback(
    async user => {
      let groups = null;
      if (syncAPICallInfo && user?.lmsId) {
        const studentGroupsCallData = {
          ...syncAPICallInfo.data,
          gradingStudentId: user.lmsId,
        };
        try {
          groups = await apiCall({
            authToken,
            path: syncAPICallInfo.path,
            data: studentGroupsCallData,
          });
        } catch (e) {
          console.error('Unable to fetch student groups from sync API');
        }
      }
      clientRPC.setFocusedUser(user, groups);
    },
    [authToken, clientRPC, syncAPICallInfo]
  );
  useEffect(() => {
    if (currentStudentIndex >= 0) {
      changeFocusedUser(students[currentStudentIndex]);
    } else {
      changeFocusedUser(null);
    }
  }, [students, changeFocusedUser, currentStudentIndex]);
  const onSelectStudent = studentIndex => {
    setCurrentStudentIndex(studentIndex);
  };
  const getCurrentStudent = () => {
    return currentStudentIndex >= 0 ? students[currentStudentIndex] : null;
  };
  return (
    <>
      <header
        className={classnames(
          'grid grid-cols-1 items-center gap-y-2 p-2',
          'lg:grid-cols-3 lg:gap-x-4 lg:px-3'
        )}
      >
        <div>
          <h1 className="text-lg p-0 m-0" data-testid="assignment-name">
            {assignmentName}
          </h1>
          <h2
            className="text-base font-medium p-0 m-0"
            data-testid="course-name"
          >
            {courseName}
          </h2>
        </div>
        <div
          className={classnames(
            'flex flex-col gap-2',
            'sm:flex-row',
            'lg:col-span-2 lg:gap-4 ' 
          )}
        >
          <div className="flex-grow-0 sm:flex-grow">
            <StudentSelector
              onSelectStudent={onSelectStudent}
              students={students}
              selectedStudentIndex={currentStudentIndex}
            />
          </div>
          <div className="flex-grow sm:flex-grow-0">
            <SubmitGradeForm student={getCurrentStudent()} />
          </div>
        </div>
      </header>
      {children}
    </>
  );
}
function LMSGrader ( { children , clientRPC , assignmentName , courseName , students : unorderedStudents , } ) { const { api : { authToken , sync : syncAPICallInfo } , } = useContext ( Config ) ; const [ currentStudentIndex , setCurrentStudentIndex ] = useState ( - 1 ) ; const students = useMemo ( ( ) => { function compareNames ( name1 = '' , name2 = '' ) { if ( name1 . toLowerCase ( ) < name2 . toLowerCase ( ) ) { return - 1 ; } else if ( name1 . toLowerCase ( ) > name2 . toLowerCase ( ) ) { return 1 ; } else { return 0 ; } } const students_ = [ ... unorderedStudents ] ; students_ . sort ( ( student1 , student2 ) => { return compareNames ( student1 . displayName , student2 . displayName ) ; } ) ; return students_ ; } , [ unorderedStudents ] ) ; const changeFocusedUser = useCallback ( async user => { let groups = null ; if ( syncAPICallInfo && user ?. lmsId ) { const studentGroupsCallData = { ... syncAPICallInfo . data , gradingStudentId : user . lmsId , } ; try { groups = await apiCall ( { authToken , path : syncAPICallInfo . path , data : studentGroupsCallData , } ) ; } catch ( e ) { console . error ( 'Unable to fetch student groups from sync API' ) ; } } clientRPC . setFocusedUser ( user , groups ) ; } , [ authToken , clientRPC , syncAPICallInfo ] ) ; useEffect ( ( ) => { if ( currentStudentIndex >= 0 ) { changeFocusedUser ( students [ currentStudentIndex ] ) ; } else { changeFocusedUser ( null ) ; } } , [ students , changeFocusedUser , currentStudentIndex ] ) ; const onSelectStudent = studentIndex => { setCurrentStudentIndex ( studentIndex ) ; } ; const getCurrentStudent = ( ) => { return currentStudentIndex >= 0 ? students [ currentStudentIndex ] : null ; } ; return ( < > < header className = { classnames ( 'grid grid-cols-1 items-center gap-y-2 p-2' , 'lg:grid-cols-3 lg:gap-x-4 lg:px-3' ) } > < div > < h1 className = "text-lg p-0 m-0" data-testid = "assignment-name" > { assignmentName } < / h1 > < h2 className = "text-base font-medium p-0 m-0" data-testid = "course-name" > { courseName } < / h2 > < / div > < div className = { classnames ( 'flex flex-col gap-2' , 'sm:flex-row' , 'lg:col-span-2 lg:gap-4 ' ) } > < div className = "flex-grow-0 sm:flex-grow" > < StudentSelector onSelectStudent = { onSelectStudent } students = { students } selectedStudentIndex = { currentStudentIndex } / > < / div > < div className = "flex-grow sm:flex-grow-0" > < SubmitGradeForm student = { getCurrentStudent ( ) } / > < / div > < / div > < / header > { children } < / > ) ; }
The LMSGrader component is fixed at the top of the page.
The LMSGrader component is fixed at the top of the page .
// No initial current student selected // Students sorted by displayName // Make a copy /**
   * Makes an RPC call to the sidebar to change to the focused user.
   */ /** @param {StudentInfo|null} user - The user to focus on in the sidebar */ // Request and set a list of groups specific to the student being graded // TODO: Improved error handling /**
   * Callback to set the current selected student.
   *
   * @param {number} studentIndex
   */ /**
   * Return the current student, or an empty object if there is none
   */ /* cols 2-3 of 3 */