From 6774b2d0b6953bcc05472732d7605df84b59d574 Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Tue, 3 Sep 2024 19:12:32 +0100 Subject: [PATCH 01/11] Fixed label --- src/dashboards/AssignmentCreator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dashboards/AssignmentCreator.tsx b/src/dashboards/AssignmentCreator.tsx index 4952e14e..bc4de1b1 100644 --- a/src/dashboards/AssignmentCreator.tsx +++ b/src/dashboards/AssignmentCreator.tsx @@ -380,7 +380,7 @@ export default function AssignmentCreator({isCreating, assignment, user, groups, Generate different exams setReleased((d) => !d)}> - Release automatically + Auto release results
From d307c61cd72a713b74c88615748eec684f21941e Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Tue, 3 Sep 2024 19:23:45 +0100 Subject: [PATCH 02/11] Added a feature to automatically start an exam --- src/dashboards/AssignmentCreator.tsx | 28 +++++++++++++++++++++++++++- src/interfaces/results.ts | 2 ++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/dashboards/AssignmentCreator.tsx b/src/dashboards/AssignmentCreator.tsx index bc4de1b1..1a0c1958 100644 --- a/src/dashboards/AssignmentCreator.tsx +++ b/src/dashboards/AssignmentCreator.tsx @@ -48,6 +48,7 @@ export default function AssignmentCreator({isCreating, assignment, user, groups, ); const [isLoading, setIsLoading] = useState(false); const [startDate, setStartDate] = useState(assignment ? moment(assignment.startDate).toDate() : new Date()); + const [endDate, setEndDate] = useState( assignment ? moment(assignment.endDate).toDate() : moment().hours(23).minutes(59).add(8, "day").toDate(), ); @@ -57,6 +58,9 @@ export default function AssignmentCreator({isCreating, assignment, user, groups, const [generateMultiple, setGenerateMultiple] = useState(false); const [released, setReleased] = useState(false); + const [autoStart, setAutostart] = useState(false); + const [autoStartDate, setAutoStartDate] = useState(assignment ? moment(assignment.autoStartDate).toDate() : new Date()); + const [useRandomExams, setUseRandomExams] = useState(true); const [examIDs, setExamIDs] = useState<{id: string; module: Module}[]>([]); @@ -90,6 +94,8 @@ export default function AssignmentCreator({isCreating, assignment, user, groups, variant, instructorGender, released, + autoStart, + autoStartDate, }) .then(() => { toast.success(`The assignment "${name}" has been ${assignment ? "updated" : "created"} successfully!`); @@ -233,7 +239,7 @@ export default function AssignmentCreator({isCreating, assignment, user, groups,
- + setEndDate(date)} />
+ {autoStart && (
+ + moment(date).isSameOrAfter(new Date())} + dateFormat="dd/MM/yyyy HH:mm" + selected={autoStartDate} + showTimeSelect + onChange={(date) => setAutoStartDate(date)} + /> +
+ )}
{selectedModules.includes("speaking") && ( @@ -382,6 +405,9 @@ export default function AssignmentCreator({isCreating, assignment, user, groups, setReleased((d) => !d)}> Auto release results + setAutostart((d) => !d)}> + Auto start exam +
- )} - {assignment && (assignment.results.length === 0 || moment().isAfter(moment(assignment.startDate))) && ( - - )} - - - - - ); + return assignment.results.length === 0; + } + + return false; + }; + + return ( + +
+ +
+
+ + Start Date:{" "} + {moment(assignment?.startDate).format("DD/MM/YY, HH:mm")} + + + End Date: {moment(assignment?.endDate).format("DD/MM/YY, HH:mm")} + +
+
+ + Assignees:{" "} + {users + .filter((u) => assignment?.assignees.includes(u.id)) + .map((u) => `${u.name} (${u.email})`) + .join(", ")} + + + Assigner:{" "} + {getUserName(users.find((x) => x.id === assignment?.assigner))} + +
+
+
+ Average Scores +
+ {assignment && + uniqBy(assignment.exams, (x) => x.module).map(({ module }) => ( +
+ {module === "reading" && } + {module === "listening" && ( + + )} + {module === "writing" && } + {module === "speaking" && } + {module === "level" && } + {calculateAverageModuleScore(module) > -1 && ( + + {calculateAverageModuleScore(module).toFixed(1)} + + )} +
+ ))} +
+
+
+ + Results ({assignment?.results.length}/{assignment?.assignees.length} + ) + +
+ {assignment && assignment?.results.length > 0 && ( +
+ {assignment.results.map((r) => + customContent(r.stats, r.user, r.type) + )} +
+ )} + {assignment && assignment?.results.length === 0 && ( + No results yet... + )} +
+
+ +
+ {assignment && + (assignment.results.length === assignment.assignees.length || + moment().isAfter(moment(assignment.endDate))) && ( + + )} + {/** if the assignment is not deemed as active yet, display start */} + {shouldRenderStart() && ( + + )} + +
+
+
+ ); } diff --git a/src/dashboards/views/AssignmentsPage.tsx b/src/dashboards/views/AssignmentsPage.tsx index c6ff9a10..19e0a8d0 100644 --- a/src/dashboards/views/AssignmentsPage.tsx +++ b/src/dashboards/views/AssignmentsPage.tsx @@ -1,150 +1,229 @@ -import {Assignment} from "@/interfaces/results"; -import {CorporateUser, Group, User} from "@/interfaces/user"; -import {getUserCompanyName} from "@/resources/user"; -import {activeAssignmentFilter, archivedAssignmentFilter, futureAssignmentFilter, pastAssignmentFilter} from "@/utils/assignments"; +import { Assignment } from "@/interfaces/results"; +import { CorporateUser, Group, User } from "@/interfaces/user"; +import { getUserCompanyName } from "@/resources/user"; +import { + activeAssignmentFilter, + archivedAssignmentFilter, + futureAssignmentFilter, + pastAssignmentFilter, + unstartedAssignmentFilter, +} from "@/utils/assignments"; import clsx from "clsx"; -import {groupBy} from "lodash"; -import {useState} from "react"; -import {BsArrowLeft, BsArrowRepeat, BsPlus} from "react-icons/bs"; +import { groupBy } from "lodash"; +import { useState } from "react"; +import { BsArrowLeft, BsArrowRepeat, BsPlus } from "react-icons/bs"; import AssignmentCard from "../AssignmentCard"; import AssignmentCreator from "../AssignmentCreator"; import AssignmentView from "../AssignmentView"; interface Props { - assignments: Assignment[]; - corporateAssignments?: ({corporate?: CorporateUser} & Assignment)[]; - groups: Group[]; - users: User[]; - isLoading: boolean; - user: User; - onBack: () => void; - reloadAssignments: () => void; + assignments: Assignment[]; + corporateAssignments?: ({ corporate?: CorporateUser } & Assignment)[]; + groups: Group[]; + users: User[]; + isLoading: boolean; + user: User; + onBack: () => void; + reloadAssignments: () => void; } -export default function AssignmentsPage({assignments, corporateAssignments, user, groups, users, isLoading, onBack, reloadAssignments}: Props) { - const [selectedAssignment, setSelectedAssignment] = useState(); - const [isCreatingAssignment, setIsCreatingAssignment] = useState(false); +export default function AssignmentsPage({ + assignments, + corporateAssignments, + user, + groups, + users, + isLoading, + onBack, + reloadAssignments, +}: Props) { + const [selectedAssignment, setSelectedAssignment] = useState(); + const [isCreatingAssignment, setIsCreatingAssignment] = useState(false); - return ( - <> - { - setSelectedAssignment(undefined); - setIsCreatingAssignment(false); - reloadAssignments(); - }} - assignment={selectedAssignment} - /> - {/** I'll be using this is creating assingment as a workaround for a key to trigger a new rendering */} - {isCreatingAssignment && { - setIsCreatingAssignment(false); - setSelectedAssignment(undefined); - reloadAssignments(); - }} - />} -
-
- - Back -
-
- Reload - -
-
-
- Active Assignments Status -
- - Total: {assignments.filter(activeAssignmentFilter).reduce((acc, curr) => acc + curr.results.length, 0)}/ - {assignments.filter(activeAssignmentFilter).reduce((acc, curr) => curr.exams.length + acc, 0)} - - {Object.keys(groupBy(corporateAssignments, (x) => x.corporate?.id)).map((x) => ( -
- {getUserCompanyName(users.find((u) => u.id === x)!, users, groups)}: - - {groupBy(corporateAssignments, (x) => x.corporate?.id)[x].reduce((acc, curr) => curr.results.length + acc, 0)}/ - {groupBy(corporateAssignments, (x) => x.corporate?.id)[x].reduce((acc, curr) => curr.exams.length + acc, 0)} - -
- ))} -
-
-
-

Active Assignments ({assignments.filter(activeAssignmentFilter).length})

-
- {assignments.filter(activeAssignmentFilter).map((a) => ( - setSelectedAssignment(a)} key={a.id} /> - ))} -
-
-
-

Planned Assignments ({assignments.filter(futureAssignmentFilter).length})

-
-
setIsCreatingAssignment(true)} - className="w-[250px] h-[200px] flex flex-col gap-2 items-center justify-center bg-white hover:bg-mti-purple-ultralight text-mti-purple-light hover:text-mti-purple-dark border border-mti-gray-platinum hover:drop-shadow p-4 cursor-pointer rounded-xl transition ease-in-out duration-300"> - - New Assignment -
- {assignments.filter(futureAssignmentFilter).map((a) => ( - { - setSelectedAssignment(a); - setIsCreatingAssignment(true); - }} - key={a.id} - /> - ))} -
-
-
-

Past Assignments ({assignments.filter(pastAssignmentFilter).length})

-
- {assignments.filter(pastAssignmentFilter).map((a) => ( - setSelectedAssignment(a)} - key={a.id} - allowDownload - reload={reloadAssignments} - allowArchive - allowExcelDownload - /> - ))} -
-
-
-

Archived Assignments ({assignments.filter(archivedAssignmentFilter).length})

-
- {assignments.filter(archivedAssignmentFilter).map((a) => ( - setSelectedAssignment(a)} - key={a.id} - allowDownload - reload={reloadAssignments} - allowUnarchive - allowExcelDownload - /> - ))} -
-
- - ); + const unstartedAssignments = assignments.filter(unstartedAssignmentFilter); + + const displayAssignmentView = !!selectedAssignment && !isCreatingAssignment; + + return ( + <> + {displayAssignmentView && ( + { + setSelectedAssignment(undefined); + setIsCreatingAssignment(false); + reloadAssignments(); + }} + assignment={selectedAssignment} + /> + )} + {/** I'll be using this is creating assingment as a workaround for a key to trigger a new rendering */} + {isCreatingAssignment && ( + { + setIsCreatingAssignment(false); + setSelectedAssignment(undefined); + reloadAssignments(); + }} + /> + )} +
+
+ + Back +
+
+ Reload + +
+
+
+ Active Assignments Status +
+ + Total:{" "} + {assignments + .filter(activeAssignmentFilter) + .reduce((acc, curr) => acc + curr.results.length, 0)} + / + {assignments + .filter(activeAssignmentFilter) + .reduce((acc, curr) => curr.exams.length + acc, 0)} + + {Object.keys( + groupBy(corporateAssignments, (x) => x.corporate?.id) + ).map((x) => ( +
+ + {getUserCompanyName( + users.find((u) => u.id === x)!, + users, + groups + )} + :{" "} + + + {groupBy(corporateAssignments, (x) => x.corporate?.id)[ + x + ].reduce((acc, curr) => curr.results.length + acc, 0)} + / + {groupBy(corporateAssignments, (x) => x.corporate?.id)[ + x + ].reduce((acc, curr) => curr.exams.length + acc, 0)} + +
+ ))} +
+
+
+

+ Active Assignments ( + {assignments.filter(activeAssignmentFilter).length}) +

+
+ {assignments.filter(activeAssignmentFilter).map((a) => ( + setSelectedAssignment(a)} + key={a.id} + /> + ))} +
+
+
+

+ Active Assignments Pending Start ({unstartedAssignments.length}) +

+
+ {unstartedAssignments.map((a) => ( + setSelectedAssignment(a)} + key={a.id} + /> + ))} +
+
+
+

+ Planned Assignments ( + {assignments.filter(futureAssignmentFilter).length}) +

+
+
setIsCreatingAssignment(true)} + className="w-[250px] h-[200px] flex flex-col gap-2 items-center justify-center bg-white hover:bg-mti-purple-ultralight text-mti-purple-light hover:text-mti-purple-dark border border-mti-gray-platinum hover:drop-shadow p-4 cursor-pointer rounded-xl transition ease-in-out duration-300" + > + + New Assignment +
+ {assignments.filter(futureAssignmentFilter).map((a) => ( + { + setSelectedAssignment(a); + setIsCreatingAssignment(true); + }} + key={a.id} + /> + ))} +
+
+
+

+ Past Assignments ({assignments.filter(pastAssignmentFilter).length}) +

+
+ {assignments.filter(pastAssignmentFilter).map((a) => ( + setSelectedAssignment(a)} + key={a.id} + allowDownload + reload={reloadAssignments} + allowArchive + allowExcelDownload + /> + ))} +
+
+
+

+ Archived Assignments ( + {assignments.filter(archivedAssignmentFilter).length}) +

+
+ {assignments.filter(archivedAssignmentFilter).map((a) => ( + setSelectedAssignment(a)} + key={a.id} + allowDownload + reload={reloadAssignments} + allowUnarchive + allowExcelDownload + /> + ))} +
+
+ + ); } diff --git a/src/utils/assignments.ts b/src/utils/assignments.ts index a4584981..6bfc6fa5 100644 --- a/src/utils/assignments.ts +++ b/src/utils/assignments.ts @@ -38,3 +38,14 @@ export const activeAssignmentFilter = (a: Assignment) => { return false; }; + +export const unstartedAssignmentFilter = (a: Assignment) => { + const currentDate = moment(); + if(moment(a.endDate).isBefore(currentDate)) return false; + if(a.archived) return false; + + if(a.autoStart && a.autoStartDate && moment(a.autoStartDate).isBefore(currentDate)) return false; + + if(!a.start) return true; + return false; +} \ No newline at end of file From 25e6cb36a98cc73a71f228a4da68f21b36be70b0 Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Tue, 3 Sep 2024 23:23:18 +0100 Subject: [PATCH 11/11] Improvements on start button --- src/dashboards/AssignmentCreator.tsx | 2 +- src/dashboards/AssignmentView.tsx | 8 ++-- src/dashboards/views/AssignmentsPage.tsx | 40 ++++++++++-------- src/utils/assignments.ts | 52 ++++++++++++++++++------ 4 files changed, 65 insertions(+), 37 deletions(-) diff --git a/src/dashboards/AssignmentCreator.tsx b/src/dashboards/AssignmentCreator.tsx index 205ba3b5..1724da6d 100644 --- a/src/dashboards/AssignmentCreator.tsx +++ b/src/dashboards/AssignmentCreator.tsx @@ -47,7 +47,7 @@ export default function AssignmentCreator({isCreating, assignment, user, groups, }), ); const [isLoading, setIsLoading] = useState(false); - const [startDate, setStartDate] = useState(assignment ? moment(assignment.startDate).toDate() : new Date()); + const [startDate, setStartDate] = useState(assignment ? moment(assignment.startDate).toDate() : moment().add(1, 'hour').toDate()); const [endDate, setEndDate] = useState( assignment ? moment(assignment.endDate).toDate() : moment().hours(23).minutes(59).add(8, "day").toDate(), diff --git a/src/dashboards/AssignmentView.tsx b/src/dashboards/AssignmentView.tsx index 7426b19d..2565e532 100644 --- a/src/dashboards/AssignmentView.tsx +++ b/src/dashboards/AssignmentView.tsx @@ -24,7 +24,7 @@ import { BsPen, } from "react-icons/bs"; import { toast } from "react-toastify"; -import { activeAssignmentFilter } from "@/utils/assignments"; +import { futureAssignmentFilter } from "@/utils/assignments"; interface Props { isOpen: boolean; @@ -296,11 +296,9 @@ export default function AssignmentView({ isOpen, assignment, onClose }: Props) { const shouldRenderStart = () => { if (assignment) { - if (activeAssignmentFilter(assignment)) { - return false; + if (futureAssignmentFilter(assignment)) { + return true; } - - return assignment.results.length === 0; } return false; diff --git a/src/dashboards/views/AssignmentsPage.tsx b/src/dashboards/views/AssignmentsPage.tsx index 19e0a8d0..48690114 100644 --- a/src/dashboards/views/AssignmentsPage.tsx +++ b/src/dashboards/views/AssignmentsPage.tsx @@ -6,7 +6,7 @@ import { archivedAssignmentFilter, futureAssignmentFilter, pastAssignmentFilter, - unstartedAssignmentFilter, + startHasExpiredAssignmentFilter, } from "@/utils/assignments"; import clsx from "clsx"; import { groupBy } from "lodash"; @@ -40,10 +40,10 @@ export default function AssignmentsPage({ const [selectedAssignment, setSelectedAssignment] = useState(); const [isCreatingAssignment, setIsCreatingAssignment] = useState(false); - const unstartedAssignments = assignments.filter(unstartedAssignmentFilter); - const displayAssignmentView = !!selectedAssignment && !isCreatingAssignment; + const assignmentsPastExpiredStart = assignments.filter(startHasExpiredAssignmentFilter); + return ( <> {displayAssignmentView && ( @@ -144,21 +144,6 @@ export default function AssignmentsPage({ ))} -
-

- Active Assignments Pending Start ({unstartedAssignments.length}) -

-
- {unstartedAssignments.map((a) => ( - setSelectedAssignment(a)} - key={a.id} - /> - ))} -
-

Planned Assignments ( @@ -203,6 +188,25 @@ export default function AssignmentsPage({ /> ))} +

+
+

+ Assignments start expired ({assignmentsPastExpiredStart.length}) +

+
+ {assignments.filter(startHasExpiredAssignmentFilter).map((a) => ( + setSelectedAssignment(a)} + key={a.id} + allowDownload + reload={reloadAssignments} + allowArchive + allowExcelDownload + /> + ))} +

diff --git a/src/utils/assignments.ts b/src/utils/assignments.ts index 6bfc6fa5..0aa2d438 100644 --- a/src/utils/assignments.ts +++ b/src/utils/assignments.ts @@ -1,15 +1,29 @@ import moment from "moment"; import {Assignment} from "@/interfaces/results"; -export const futureAssignmentFilter = (a: Assignment) => { - if(a.archived) return false; - if(a.start) return false; +// export const futureAssignmentFilter = (a: Assignment) => { +// if(a.archived) return false; +// if(a.start) return false; +// const currentDate = moment(); +// const startDate = moment(a.startDate); +// if(currentDate.isAfter(startDate)) return false; +// if(a.autoStart && a.autoStartDate) { +// return moment(a.autoStartDate).isAfter(currentDate); +// } +// return false; +// } + +export const futureAssignmentFilter = (a: Assignment) => { const currentDate = moment(); - const startDate = moment(a.startDate); - if(startDate.isAfter(currentDate)) return true; - if(a.autoStart && a.autoStartDate) { - return moment(a.autoStartDate).isAfter(currentDate); + if(moment(a.endDate).isBefore(currentDate)) return false; + if(a.archived) return false; + + if(a.autoStart && a.autoStartDate && moment(a.autoStartDate).isBefore(currentDate)) return false; + + if(!a.start) { + if(moment(a.startDate).isBefore(currentDate)) return false; + return true; } return false; } @@ -36,16 +50,28 @@ export const activeAssignmentFilter = (a: Assignment) => { return moment(a.autoStartDate).isBefore(currentDate); } + // if(currentDate.isAfter(moment(a.startDate))) return true; return false; }; -export const unstartedAssignmentFilter = (a: Assignment) => { +// export const unstartedAssignmentFilter = (a: Assignment) => { +// const currentDate = moment(); +// if(moment(a.endDate).isBefore(currentDate)) return false; +// if(a.archived) return false; + +// if(a.autoStart && a.autoStartDate && moment(a.autoStartDate).isBefore(currentDate)) return false; + +// if(!a.start) { +// if(moment(a.startDate).isBefore(currentDate)) return false; +// return true; +// } +// return false; +// } + +export const startHasExpiredAssignmentFilter = (a: Assignment) => { const currentDate = moment(); - if(moment(a.endDate).isBefore(currentDate)) return false; if(a.archived) return false; - - if(a.autoStart && a.autoStartDate && moment(a.autoStartDate).isBefore(currentDate)) return false; - - if(!a.start) return true; + if(a.start) return false; + if(currentDate.isAfter(moment(a.startDate)) && currentDate.isBefore(moment(a.endDate))) return true; return false; } \ No newline at end of file