Loading frontend/src/components/Questionnaire/index.tsx +1 −1 Original line number Diff line number Diff line Loading @@ -198,7 +198,7 @@ const Questionnaire: FC<QuestionnaireProps> = ({ <p> {maxAttempts === undefined ? 'Unlimited attempts: submit is available until all answers are correct' : `Remaining attempts: ${maxAttempts - submissionCount} out of ${maxAttempts}`} : `Remaining attempts: ${maxAttempts - submissionCount} of ${maxAttempts}`} </p> )} </div> Loading frontend/src/instructor/InstructorQuestionnaire/InstructorQuestionnaireCard.tsx +123 −40 Original line number Diff line number Diff line import { Section, SectionCard } from '@blueprintjs/core' import { Button, Section, SectionCard } from '@blueprintjs/core' import { css } from '@emotion/css' import type { Questionnaire, TeamQuestionnaireState } from '@inject/graphql' import type { NavigateOptions } from '@tanstack/react-router' import { type FC } from 'react' import { useEffect, useRef, useState, type FC } from 'react' import InstructorQuestionnaire from '.' import Status from './Status' import { canBeReviewed } from './utils' // TODO: add individual submissions import { canBeReviewed, parseMaxAttempts } from './utils' const objectiveClass = css` flex-shrink: 0; Loading Loading @@ -35,7 +33,30 @@ export const InstructorQuestionnaireCard: FC< hideReview, getFileLink, initialOpen = true, }) => ( }) => { const submissionCount = teamState.submissions.length const [submissionIndex, setSubmissionIndex] = useState( submissionCount > 0 ? submissionCount - 1 : undefined ) const teamIdRef = useRef(teamId) const submissionCountRef = useRef(submissionCount) useEffect(() => { if (teamIdRef.current === teamId) { if (submissionCountRef.current < submissionCount) { setSubmissionIndex(submissionCount - 1) return } return } setSubmissionIndex(submissionCount > 0 ? submissionCount - 1 : undefined) teamIdRef.current = teamId }, [submissionCount, teamId]) const answersAccepted = teamState.acceptedAnswers.length !== 0 const maxAttempts = parseMaxAttempts(questionnaire.repeatable?.maxAttempts) return ( <Section key={questionnaire.id} title={questionnaire.displayName} Loading @@ -51,6 +72,61 @@ export const InstructorQuestionnaireCard: FC< defaultIsOpen: initialOpen, }} > <SectionCard className={css` display: flex; flex-direction: column; gap: 0.5rem; align-items: center; justify-content: space-between; `} > <div> {maxAttempts === undefined ? 'Unlimited attempts: submit is available until all answers are correct' : `Remaining attempts: ${maxAttempts - submissionCount} of ${maxAttempts}`} </div> <div> {answersAccepted ? 'Answers accepted' : 'Answers not accepted'} </div> <div className={css` display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; `} > <Button minimal icon='chevron-left' disabled={submissionIndex === undefined || submissionIndex === 0} onClick={() => { if (submissionIndex !== undefined) { setSubmissionIndex(submissionIndex - 1) } }} /> {submissionIndex === undefined ? 'No answers submitted yet' : `Answer submission: ${ submissionIndex !== undefined ? submissionIndex + 1 : 'N/A' } of ${teamState.submissions.length}`} <Button minimal icon='chevron-right' disabled={ submissionIndex === undefined || submissionIndex === teamState.submissions.length - 1 } onClick={() => { if (submissionIndex !== undefined) { setSubmissionIndex(submissionIndex + 1) } }} /> </div> </SectionCard> <SectionCard padded> <InstructorQuestionnaire getFileLink={getFileLink} Loading @@ -61,13 +137,20 @@ export const InstructorQuestionnaireCard: FC< status={teamState.status} content={questionnaire.content} questions={questionnaire.questions} teamAnswers={teamState.acceptedAnswers} teamAnswers={ submissionIndex !== undefined && // can be bigger if the teamId changes submissionIndex < teamState.submissions.length ? teamState.submissions[submissionIndex].answers : [] } relatedMilestones={teamState.relatedMilestones} hideReview={hideReview} submissionCount={teamState.submissions.length} submissionCount={submissionCount} maxAttempts={questionnaire.repeatable?.maxAttempts} answersAccepted={teamState.acceptedAnswers.length !== 0} answersAccepted={answersAccepted} /> </SectionCard> </Section> ) } graphql/fragments.ts +4 −0 Original line number Diff line number Diff line Loading @@ -1083,6 +1083,10 @@ export const TeamQuestionnaireState = graphql( submissions { id attempt accepted answers { ...QuestionnaireAnswer } } relatedMilestones { ...QuestionRelatedMilestones Loading graphql/graphql-cache.d.ts +15 −15 File changed.Preview size limit exceeded, changes collapsed. Show changes graphql/urql/worker/cacheConsumer.ts +7 −0 Original line number Diff line number Diff line Loading @@ -740,6 +740,13 @@ const cache: Exchange = offlineExchange<GraphCacheConfig>({ { exerciseId: args.exerciseId as string }, reverse(newArr) ) if (actionLog.type === 'FORM_SUBMISSION') { cache.invalidate('Query', 'teamQuestionnaireState', { teamId: actionLog.team.id, }) } commitActionLogToCache(cache, actionLog) } break Loading Loading
frontend/src/components/Questionnaire/index.tsx +1 −1 Original line number Diff line number Diff line Loading @@ -198,7 +198,7 @@ const Questionnaire: FC<QuestionnaireProps> = ({ <p> {maxAttempts === undefined ? 'Unlimited attempts: submit is available until all answers are correct' : `Remaining attempts: ${maxAttempts - submissionCount} out of ${maxAttempts}`} : `Remaining attempts: ${maxAttempts - submissionCount} of ${maxAttempts}`} </p> )} </div> Loading
frontend/src/instructor/InstructorQuestionnaire/InstructorQuestionnaireCard.tsx +123 −40 Original line number Diff line number Diff line import { Section, SectionCard } from '@blueprintjs/core' import { Button, Section, SectionCard } from '@blueprintjs/core' import { css } from '@emotion/css' import type { Questionnaire, TeamQuestionnaireState } from '@inject/graphql' import type { NavigateOptions } from '@tanstack/react-router' import { type FC } from 'react' import { useEffect, useRef, useState, type FC } from 'react' import InstructorQuestionnaire from '.' import Status from './Status' import { canBeReviewed } from './utils' // TODO: add individual submissions import { canBeReviewed, parseMaxAttempts } from './utils' const objectiveClass = css` flex-shrink: 0; Loading Loading @@ -35,7 +33,30 @@ export const InstructorQuestionnaireCard: FC< hideReview, getFileLink, initialOpen = true, }) => ( }) => { const submissionCount = teamState.submissions.length const [submissionIndex, setSubmissionIndex] = useState( submissionCount > 0 ? submissionCount - 1 : undefined ) const teamIdRef = useRef(teamId) const submissionCountRef = useRef(submissionCount) useEffect(() => { if (teamIdRef.current === teamId) { if (submissionCountRef.current < submissionCount) { setSubmissionIndex(submissionCount - 1) return } return } setSubmissionIndex(submissionCount > 0 ? submissionCount - 1 : undefined) teamIdRef.current = teamId }, [submissionCount, teamId]) const answersAccepted = teamState.acceptedAnswers.length !== 0 const maxAttempts = parseMaxAttempts(questionnaire.repeatable?.maxAttempts) return ( <Section key={questionnaire.id} title={questionnaire.displayName} Loading @@ -51,6 +72,61 @@ export const InstructorQuestionnaireCard: FC< defaultIsOpen: initialOpen, }} > <SectionCard className={css` display: flex; flex-direction: column; gap: 0.5rem; align-items: center; justify-content: space-between; `} > <div> {maxAttempts === undefined ? 'Unlimited attempts: submit is available until all answers are correct' : `Remaining attempts: ${maxAttempts - submissionCount} of ${maxAttempts}`} </div> <div> {answersAccepted ? 'Answers accepted' : 'Answers not accepted'} </div> <div className={css` display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; `} > <Button minimal icon='chevron-left' disabled={submissionIndex === undefined || submissionIndex === 0} onClick={() => { if (submissionIndex !== undefined) { setSubmissionIndex(submissionIndex - 1) } }} /> {submissionIndex === undefined ? 'No answers submitted yet' : `Answer submission: ${ submissionIndex !== undefined ? submissionIndex + 1 : 'N/A' } of ${teamState.submissions.length}`} <Button minimal icon='chevron-right' disabled={ submissionIndex === undefined || submissionIndex === teamState.submissions.length - 1 } onClick={() => { if (submissionIndex !== undefined) { setSubmissionIndex(submissionIndex + 1) } }} /> </div> </SectionCard> <SectionCard padded> <InstructorQuestionnaire getFileLink={getFileLink} Loading @@ -61,13 +137,20 @@ export const InstructorQuestionnaireCard: FC< status={teamState.status} content={questionnaire.content} questions={questionnaire.questions} teamAnswers={teamState.acceptedAnswers} teamAnswers={ submissionIndex !== undefined && // can be bigger if the teamId changes submissionIndex < teamState.submissions.length ? teamState.submissions[submissionIndex].answers : [] } relatedMilestones={teamState.relatedMilestones} hideReview={hideReview} submissionCount={teamState.submissions.length} submissionCount={submissionCount} maxAttempts={questionnaire.repeatable?.maxAttempts} answersAccepted={teamState.acceptedAnswers.length !== 0} answersAccepted={answersAccepted} /> </SectionCard> </Section> ) }
graphql/fragments.ts +4 −0 Original line number Diff line number Diff line Loading @@ -1083,6 +1083,10 @@ export const TeamQuestionnaireState = graphql( submissions { id attempt accepted answers { ...QuestionnaireAnswer } } relatedMilestones { ...QuestionRelatedMilestones Loading
graphql/graphql-cache.d.ts +15 −15 File changed.Preview size limit exceeded, changes collapsed. Show changes
graphql/urql/worker/cacheConsumer.ts +7 −0 Original line number Diff line number Diff line Loading @@ -740,6 +740,13 @@ const cache: Exchange = offlineExchange<GraphCacheConfig>({ { exerciseId: args.exerciseId as string }, reverse(newArr) ) if (actionLog.type === 'FORM_SUBMISSION') { cache.invalidate('Query', 'teamQuestionnaireState', { teamId: actionLog.team.id, }) } commitActionLogToCache(cache, actionLog) } break Loading