Skip to content

Commit f602ae5

Browse files
yl9431cowsed
andauthored
passed trainings results page (#1061)
Co-authored-by: Richard Sommers <44383226+cowsed@users.noreply.github.com>
1 parent 5d6553f commit f602ae5

9 files changed

Lines changed: 107 additions & 30 deletions

File tree

client/src/AppRoutes.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ export default function AppRoutes() {
188188
<Route path="/maker/training" element={<TrainingPage />} />
189189
<Route path="/maker/training/:id" element={<QuizPage />} />
190190
<Route path="/maker/training/:id/results" element={<QuizResults />} />
191+
<Route path="/maker/training/:id/results/:submissionID" element={<QuizResults />} />
191192

192193
<Route path="/maker/materials" element={<InventoryPreviewPage />} />
193194

@@ -212,4 +213,4 @@ export default function AppRoutes() {
212213
/>
213214
</div>
214215
);
215-
}
216+
}

client/src/common/ModuleStatusRow.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@ import LockClockIcon from '@mui/icons-material/LockClock';
77
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
88
import CloseIcon from "@mui/icons-material/Close";
99
import { format } from "date-fns";
10+
import { useQuery } from "@apollo/client";
11+
import { GET_ACCESS_PROGRESSES } from "../queries/trainingQueries";
12+
import { GET_PASSED_SUBMISSION } from "../queries/getSubmissions";
1013

1114
interface ModuleStatusRowProps {
1215
ms: ModuleStatus
1316
}
1417

1518
export default function ModuleStatusRow(props: ModuleStatusRowProps) {
1619
const navigate = useNavigate();
20+
const passedSubmission = useQuery(GET_PASSED_SUBMISSION, {variables: {moduleID: props.ms.moduleID}});
21+
const navigateUrl = (!passedSubmission?.data?.passingSubmission?.id) ? `/maker/training/${props.ms.moduleID}` : `/maker/training/${props.ms.moduleID}/results/${passedSubmission?.data?.passingSubmission?.id}`
1722

1823
return (
19-
<CardActionArea onClick={() => navigate(`/maker/training/${props.ms.moduleID}`)} sx={{ width: "unset" }}>
24+
<CardActionArea onClick={() => navigate(navigateUrl)} sx={{ width: "unset" }}>
2025
<Stack direction="row" spacing={1} alignItems="center" padding="10px" width="100%">
2126
{
2227
props.ms.status === "Passed"

client/src/pages/maker/take_quiz/QuizResults.tsx

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,80 @@ import { useQuery } from "@apollo/client";
22
import { Stack } from "@mui/material";
33
import { useParams } from "react-router-dom";
44
import RequestWrapper from "../../../common/RequestWrapper";
5-
import { GET_LATEST_SUBMISSION } from "../../../queries/getSubmissions";
5+
import { GET_LATEST_SUBMISSION, GET_SUBMISSION } from "../../../queries/getSubmissions";
66
import { GET_MODULE } from "../../../queries/trainingQueries";
77
import { Module } from "../../../types/Quiz";
88
import SubmissionCard from "./SubmissionCard";
99
import ResultsCard from "./ResultsCard";
1010
import EquipmentProgressCard from "./EquipmentProgessCard";
1111
import RetakeQuiz from "./RetakeQuiz";
1212
import { useIsMobile } from "../../../common/IsMobileProvider";
13+
import { useCurrentUser } from "../../../common/CurrentUserProvider";
1314

1415
export default function QuizResults() {
1516
const { id } = useParams<{ id: string }>();
16-
const submissionResult = useQuery(GET_LATEST_SUBMISSION,
17-
{
18-
variables: { moduleID: id },
19-
fetchPolicy: 'network-only', // Prevents caching previous submissions if multiple attempts are made in one session
20-
nextFetchPolicy: 'cache-first' // Caches this submission while we are using it
17+
const {submissionID} = useParams <{submissionID: string }>();
18+
const currentUser = useCurrentUser();
19+
const currentSubmissionResult = useQuery(GET_LATEST_SUBMISSION,
20+
{
21+
variables: { moduleID: id },
22+
fetchPolicy: 'network-only', // Prevents caching previous submissions if multiple attempts are made in one session
23+
nextFetchPolicy: 'cache-first' // Caches this submission while we are using it
24+
}
25+
);
26+
const passedSubmissionResult = useQuery(GET_SUBMISSION,
27+
{
28+
variables: { submissionID: submissionID },
29+
fetchPolicy: 'network-only',
30+
nextFetchPolicy: 'cache-first'
2131
}
2232
);
33+
2334
const moduleResult = useQuery<{module: Module}>(
2435
GET_MODULE,
2536
{
2637
variables: { id }
2738
}
2839
);
29-
3040
const isMobile = useIsMobile();
31-
3241
return (
3342
<RequestWrapper
34-
loading={submissionResult.loading || submissionResult.data === undefined}
35-
error={submissionResult.error}
43+
loading={currentSubmissionResult.loading || currentSubmissionResult.data === undefined}
44+
error={currentSubmissionResult.error || currentSubmissionResult.error}
3645
>
3746
<RequestWrapper
3847
loading={moduleResult.loading || moduleResult.data === undefined}
3948
error={moduleResult.error}
4049
>
4150
<Stack spacing={2} justifyContent={"center"} margin={"30px 45px"}>
51+
{ submissionID && passedSubmissionResult?.data?.submission.makerID === currentUser.id ?
4252
<Stack direction={"row"} alignItems={"flex-start"} width={"100%"} >
4353
<Stack direction="column" width={isMobile ? "100%" : "50%"}>
44-
<SubmissionCard module={moduleResult.data?.module!} submission={submissionResult.data?.latestSubmission} />
45-
{isMobile && moduleResult.data && !submissionResult.data?.latestSubmission.passed ? <RetakeQuiz moduleID={moduleResult.data?.module.id}></RetakeQuiz> : <></>}
54+
<SubmissionCard module={moduleResult.data?.module!} submission={passedSubmissionResult.data?.submission} />
55+
{isMobile && moduleResult.data ? <RetakeQuiz moduleID={moduleResult.data?.module.id} ></RetakeQuiz> : <></>}
56+
{isMobile && moduleResult.data && <EquipmentProgressCard moduleID={moduleResult.data?.module.id} />}
57+
<ResultsCard summary={passedSubmissionResult.data?.submission.summary}></ResultsCard>
58+
</Stack>
59+
<Stack direction="column" width={isMobile ? "100%" : "50%"}>
60+
{!isMobile && moduleResult.data ? <RetakeQuiz moduleID={moduleResult.data?.module.id} ></RetakeQuiz> : <></>}
61+
{!isMobile && moduleResult.data && <EquipmentProgressCard moduleID={moduleResult.data?.module.id} />}
62+
</Stack>
63+
</Stack>
64+
:
65+
<Stack direction={"row"} alignItems={"flex-start"} width={"100%"} >
66+
<Stack direction="column" width={isMobile ? "100%" : "50%"}>
67+
<SubmissionCard module={moduleResult.data?.module!} submission={currentSubmissionResult.data?.latestSubmission} />
68+
{isMobile && moduleResult.data ? <RetakeQuiz moduleID={moduleResult.data?.module.id} ></RetakeQuiz> : <></>}
4669
{isMobile && moduleResult.data && <EquipmentProgressCard moduleID={moduleResult.data?.module.id} />}
47-
<ResultsCard summary={submissionResult.data?.latestSubmission.summary}></ResultsCard>
70+
<ResultsCard summary={currentSubmissionResult.data?.latestSubmission.summary}></ResultsCard>
4871
</Stack>
4972
<Stack direction="column" width={isMobile ? "100%" : "50%"}>
50-
{!isMobile && !submissionResult.data?.latestSubmission.passed && moduleResult.data ? <RetakeQuiz moduleID={moduleResult.data?.module.id}></RetakeQuiz> : <></>}
73+
{!isMobile && moduleResult.data ? <RetakeQuiz moduleID={moduleResult.data?.module.id} ></RetakeQuiz> : <></>}
5174
{!isMobile && moduleResult.data && <EquipmentProgressCard moduleID={moduleResult.data?.module.id} />}
5275
</Stack>
53-
</Stack>
76+
</Stack>}
5477
</Stack>
5578
</RequestWrapper>
5679
</RequestWrapper>
5780
);
58-
}
81+
}

client/src/pages/maker/take_quiz/RetakeQuiz.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useQuery } from "@apollo/client";
22
import { Card, CardContent, CardHeader, Link, Typography } from "@mui/material";
3-
import { GET_REMAINING_SUBMISSIONS } from "../../../queries/getSubmissions";
3+
import { GET_PASSED_SUBMISSION, GET_REMAINING_SUBMISSIONS } from "../../../queries/getSubmissions";
44
import { useIsMobile } from "../../../common/IsMobileProvider";
55
import { Stack } from "@mui/system";
66
import RequestWrapper from "../../../common/RequestWrapper";
@@ -14,6 +14,9 @@ export default function RetakeQuiz(props: { moduleID: number }) {
1414
fetchPolicy: 'network-only',
1515
}
1616
);
17+
const passedSubmission = useQuery(GET_PASSED_SUBMISSION, {variables: {moduleID: props.moduleID}});
18+
const expirationDate = new Date(+(passedSubmission?.data?.passingSubmission?.expirationDate)).toLocaleString('en-US');
19+
const remainingAttempts = Number(submissions.data?.remainingSubmissions.submissionLimit) - Number(submissions.data?.remainingSubmissions.failedSubmissions)
1720

1821
return (
1922
<RequestWrapper loading={ submissions.loading || submissions.data === undefined }
@@ -23,15 +26,16 @@ export default function RetakeQuiz(props: { moduleID: number }) {
2326
<CardContent>
2427
<Card>
2528
<CardContent>
26-
{submissions.data?.remainingSubmissions.failedSubmissions >= submissions.data?.remainingSubmissions.submissionLimit ?
29+
{passedSubmission?.data?.passingSubmission ? <Typography>Your training expires on {expirationDate}</Typography> : <></>}
30+
{remainingAttempts <= 0 ?
2731
<Stack>
2832
<Typography><b>This training has been locked due to too many attempts. </b></Typography>
2933
<Typography>You will be unable to progress and submit this quiz until <b>tomorrow</b>.</Typography>
3034
<Typography>If you would like to unlock this training early and seek help with the quiz, please see a Makerspace Mentor.</Typography>
3135
</Stack>
3236
: <Stack>
33-
<Typography>Click <Link href={`/app/maker/training/${props.moduleID}`}>here</Link> to retake this quiz.</Typography>
34-
<Typography component="div">You have {submissions.data?.remainingSubmissions.submissionLimit - submissions.data?.remainingSubmissions.failedSubmissions} attempts remaining today.</Typography>
37+
<Typography>Click <Link href={`/app/maker/training/${props.moduleID}`}>here</Link> {passedSubmission?.data?.passingSubmission? <>if you would like to renew your training.</>: <>to retake this quiz.</>}</Typography>
38+
<Typography component="div">You have {remainingAttempts} {remainingAttempts === 1 ? <>attempt</> : <>attempts</>} remaining today.</Typography>
3539
</Stack>
3640
}
3741
</CardContent>

client/src/pages/maker/take_quiz/SubmissionCard.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@ interface SubmissionCardProps {
1414

1515
export default function SubmissionCard({ module, submission }: SubmissionCardProps) {
1616
const isMobile = useIsMobile();
17-
18-
1917
const submissionDate = new Date(+(submission.submissionDate)).toLocaleString('en-US');
18+
2019
return (
2120
<Card sx={{ width: (isMobile ? "90vw" : 0.85) }}>
2221
<CardContent>
@@ -96,4 +95,4 @@ export default function SubmissionCard({ module, submission }: SubmissionCardPro
9695
</CardContent>
9796
</Card>
9897
);
99-
}
98+
}

client/src/queries/getSubmissions.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import { gql } from "@apollo/client";
22

33
export const GET_SUBMISSION = gql`
44
query GetSubmission($submissionID: ID) {
5-
submission {
5+
submission (submissionID: $submissionID){
66
id
77
moduleID
88
makerID
99
submissionDate
1010
passed
1111
expirationDate
12+
summary
1213
}
1314
}
1415
`;
@@ -47,4 +48,18 @@ export const GET_REMAINING_SUBMISSIONS = gql`
4748
submissionLimit
4849
}
4950
}
51+
`;
52+
53+
export const GET_PASSED_SUBMISSION = gql `
54+
query GetSubmission($moduleID: ID) {
55+
passingSubmission(moduleID: $moduleID) {
56+
id
57+
moduleID
58+
makerID
59+
submissionDate
60+
passed
61+
expirationDate
62+
summary
63+
}
64+
}
5065
`;

server/src/repositories/Training/SubmissionRepository.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export async function getSubmission(
4444
.first();
4545

4646
if (!submission) throw new EntityNotFound("Could not find submission id ${submissionID}");
47-
4847
return submission;
4948
}
5049

@@ -137,4 +136,19 @@ export async function getFailedSubmissionsTodayByModuleAndUser(moduleID: number,
137136
today.setMinutes(0);
138137
today.setSeconds(0)
139138
return await knex("ModuleSubmissions").select().where({moduleID, makerID: userID, passed: false}).andWhere("submissionDate", ">=", today);
139+
}
140+
141+
/**
142+
* Fetch the most recent active passed submission
143+
* @param moduleId ID of module to check
144+
* @param userID ID of user to check
145+
* @returns the most recent active passed submission
146+
*/
147+
export async function getPassingSubmission(moduleID: number, userID: number): Promise<ModuleSubmissionRow | undefined> {
148+
var today = new Date();
149+
const submission = await knex("ModuleSubmissions").select()
150+
.where({moduleID, makerID: userID, passed: true}).andWhere("submissionDate", "<=", today).andWhere("expirationDate", ">", today )
151+
.orderBy("submissionDate", "desc")
152+
.first();
153+
return submission;
140154
}

server/src/resolvers/trainingSubmissionResolver.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,22 @@ const TrainingSubmissionResolvers = {
7272
return {"failedSubmissions" : failedSubmissions, "submissionLimit" : Number(process.env.TRAINING_MAX_ATTEMPTS_PER_DAY_BEFORE_LOCK)};
7373
}),
7474

75+
/**
76+
* Fetch most recent passing submissions within a year made by a user for a module
77+
* @argument moduleId ID of the TrainingModule
78+
* @returns ModuleSubmission
79+
* @throws GraphQLError if not authenticated or is on hold
80+
*/
81+
passingSubmission: async (
82+
_parent: any,
83+
args: { moduleID: string },
84+
{ ifAuthenticated }: ApolloContext
85+
) =>
86+
ifAuthenticated (async (user: any) => {
87+
return await SubmissionRepo.getPassingSubmission(Number(args.moduleID), user.id)
88+
}),
89+
7590
}
7691
};
7792

78-
export default TrainingSubmissionResolvers;
93+
export default TrainingSubmissionResolvers;

server/src/schemas/trainingSubmissionSchema.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ export const TrainingSubmissionTypeDefs = gql`
3232
}
3333
3434
extend type Query {
35-
submission(submissionID: ID): [Submission]
35+
submission(submissionID: ID): Submission
3636
submissions(moduleID: ID): [Submission]
3737
latestSubmission(moduleID: ID): Submission
3838
remainingSubmissions(moduleID: ID): remainingSubmissions
39+
passingSubmission(moduleID: ID): Submission
3940
}
40-
`;
41+
`;

0 commit comments

Comments
 (0)