Added an initial filter for corporate on records

This commit is contained in:
Joao Ramos
2024-06-21 23:22:49 +01:00
parent 61a86394ed
commit e79139174b

View File

@@ -18,7 +18,17 @@ import {sortByModule} from "@/utils/moduleUtils";
import Layout from "@/components/High/Layout"; import Layout from "@/components/High/Layout";
import clsx from "clsx"; import clsx from "clsx";
import { calculateBandScore } from "@/utils/score"; import { calculateBandScore } from "@/utils/score";
import {BsBook, BsClipboard, BsClock, BsHeadphones, BsMegaphone, BsPen, BsPersonDash, BsPersonFillX, BsXCircle} from "react-icons/bs"; import {
BsBook,
BsClipboard,
BsClock,
BsHeadphones,
BsMegaphone,
BsPen,
BsPersonDash,
BsPersonFillX,
BsXCircle,
} from "react-icons/bs";
import Select from "@/components/Low/Select"; import Select from "@/components/Low/Select";
import useGroups from "@/hooks/useGroups"; import useGroups from "@/hooks/useGroups";
import { shouldRedirectHome } from "@/utils/navigation.disabled"; import { shouldRedirectHome } from "@/utils/navigation.disabled";
@@ -52,16 +62,28 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
}; };
}, sessionOptions); }, sessionOptions);
const defaultSelectableCorporate = {
value: "",
label: "All",
};
export default function History({ user }: { user: User }) { export default function History({ user }: { user: User }) {
const [statsUserId, setStatsUserId] = useRecordStore((state) => [state.selectedUser, state.setSelectedUser]); const [statsUserId, setStatsUserId] = useRecordStore((state) => [
state.selectedUser,
state.setSelectedUser,
]);
// const [statsUserId, setStatsUserId] = useState<string | undefined>(user.id); // const [statsUserId, setStatsUserId] = useState<string | undefined>(user.id);
const [groupedStats, setGroupedStats] = useState<{ [key: string]: Stat[] }>(); const [groupedStats, setGroupedStats] = useState<{ [key: string]: Stat[] }>();
const [filter, setFilter] = useState<"months" | "weeks" | "days" | "assignments">(); const [filter, setFilter] = useState<
"months" | "weeks" | "days" | "assignments"
>();
const { assignments } = useAssignments({}); const { assignments } = useAssignments({});
const { users } = useUsers(); const { users } = useUsers();
const { stats, isLoading: isStatsLoading } = useStats(statsUserId); const { stats, isLoading: isStatsLoading } = useStats(statsUserId);
const {groups} = useGroups(user.id); const { groups: allGroups } = useGroups();
const groups = allGroups.filter((x) => x.admin === user.id);
const setExams = useExamStore((state) => state.setExams); const setExams = useExamStore((state) => state.setExams);
const setShowSolutions = useExamStore((state) => state.setShowSolutions); const setShowSolutions = useExamStore((state) => state.setShowSolutions);
@@ -84,16 +106,16 @@ export default function History({user}: {user: User}) {
) )
return false; return false;
return true; return true;
}), })
), )
); );
} }
}, [stats, isStatsLoading]); }, [stats, isStatsLoading]);
useEffect(() => { // useEffect(() => {
// just set this initially // // just set this initially
if(!statsUserId) setStatsUserId(user.id); // if (!statsUserId) setStatsUserId(user.id);
}, []); // }, []);
const toggleFilter = (value: "months" | "weeks" | "days" | "assignments") => { const toggleFilter = (value: "months" | "weeks" | "days" | "assignments") => {
setFilter((prev) => (prev === value ? undefined : value)); setFilter((prev) => (prev === value ? undefined : value));
@@ -107,7 +129,8 @@ export default function History({user}: {user: User}) {
const filteredStats: { [key: string]: Stat[] } = {}; const filteredStats: { [key: string]: Stat[] } = {};
Object.keys(stats).forEach((timestamp) => { Object.keys(stats).forEach((timestamp) => {
if (timestamp >= filterDate) filteredStats[timestamp] = stats[timestamp]; if (timestamp >= filterDate)
filteredStats[timestamp] = stats[timestamp];
}); });
return filteredStats; return filteredStats;
@@ -117,8 +140,14 @@ export default function History({user}: {user: User}) {
const filteredStats: { [key: string]: Stat[] } = {}; const filteredStats: { [key: string]: Stat[] } = {};
Object.keys(stats).forEach((timestamp) => { Object.keys(stats).forEach((timestamp) => {
if (stats[timestamp].map((s) => s.assignment === undefined).includes(false)) if (
filteredStats[timestamp] = [...stats[timestamp].filter((s) => !!s.assignment)]; stats[timestamp]
.map((s) => s.assignment === undefined)
.includes(false)
)
filteredStats[timestamp] = [
...stats[timestamp].filter((s) => !!s.assignment),
];
}); });
return filteredStats; return filteredStats;
@@ -134,8 +163,12 @@ export default function History({user}: {user: User}) {
return date.format(formatter); return date.format(formatter);
}; };
const aggregateScoresByModule = (stats: Stat[]): {module: Module; total: number; missing: number; correct: number}[] => { const aggregateScoresByModule = (
const scores: {[key in Module]: {total: number; missing: number; correct: number}} = { stats: Stat[]
): { module: Module; total: number; missing: number; correct: number }[] => {
const scores: {
[key in Module]: { total: number; missing: number; correct: number };
} = {
reading: { reading: {
total: 0, total: 0,
correct: 0, correct: 0,
@@ -180,10 +213,21 @@ export default function History({user}: {user: User}) {
if (!groupedStats) return <></>; if (!groupedStats) return <></>;
const dateStats = groupedStats[timestamp]; const dateStats = groupedStats[timestamp];
const correct = dateStats.reduce((accumulator, current) => accumulator + current.score.correct, 0); const correct = dateStats.reduce(
const total = dateStats.reduce((accumulator, current) => accumulator + current.score.total, 0); (accumulator, current) => accumulator + current.score.correct,
const aggregatedScores = aggregateScoresByModule(dateStats).filter((x) => x.total > 0); 0
const assignmentID = dateStats.reduce((_, current) => current.assignment as any, ""); );
const total = dateStats.reduce(
(accumulator, current) => accumulator + current.score.total,
0
);
const aggregatedScores = aggregateScoresByModule(dateStats).filter(
(x) => x.total > 0
);
const assignmentID = dateStats.reduce(
(_, current) => current.assignment as any,
""
);
const assignment = assignments.find((a) => a.id === assignmentID); const assignment = assignments.find((a) => a.id === assignmentID);
const isDisabled = dateStats.some((x) => x.isDisabled); const isDisabled = dateStats.some((x) => x.isDisabled);
@@ -212,7 +256,7 @@ export default function History({user}: {user: User}) {
exams exams
.map((x) => x!) .map((x) => x!)
.sort(sortByModule) .sort(sortByModule)
.map((x) => x!.module), .map((x) => x!.module)
); );
router.push("/exercises"); router.push("/exercises");
} }
@@ -222,7 +266,7 @@ export default function History({user}: {user: User}) {
const textColor = clsx( const textColor = clsx(
correct / total >= 0.7 && "text-mti-purple", correct / total >= 0.7 && "text-mti-purple",
correct / total >= 0.3 && correct / total < 0.7 && "text-mti-red", correct / total >= 0.3 && correct / total < 0.7 && "text-mti-red",
correct / total < 0.3 && "text-mti-rose", correct / total < 0.3 && "text-mti-rose"
); );
const content = ( const content = (
@@ -232,12 +276,18 @@ export default function History({user}: {user: User}) {
<span className="font-medium">{formatTimestamp(timestamp)}</span> <span className="font-medium">{formatTimestamp(timestamp)}</span>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{!!timeSpent && ( {!!timeSpent && (
<span className="text-sm flex gap-2 items-center tooltip" data-tip="Time Spent"> <span
className="text-sm flex gap-2 items-center tooltip"
data-tip="Time Spent"
>
<BsClock /> {Math.floor(timeSpent / 60)} minutes <BsClock /> {Math.floor(timeSpent / 60)} minutes
</span> </span>
)} )}
{!!inactivity && ( {!!inactivity && (
<span className="text-sm flex gap-2 items-center tooltip" data-tip="Inactivity"> <span
className="text-sm flex gap-2 items-center tooltip"
data-tip="Inactivity"
>
<BsXCircle /> {Math.floor(inactivity / 60)} minutes <BsXCircle /> {Math.floor(inactivity / 60)} minutes
</span> </span>
)} )}
@@ -246,7 +296,12 @@ export default function History({user}: {user: User}) {
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
<span className={textColor}> <span className={textColor}>
Level{" "} Level{" "}
{(aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length).toFixed(1)} {(
aggregatedLevels.reduce(
(accumulator, current) => accumulator + current.level,
0
) / aggregatedLevels.length
).toFixed(1)}
</span> </span>
{renderPdfIcon(session, textColor, textColor)} {renderPdfIcon(session, textColor, textColor)}
</div> </div>
@@ -263,8 +318,9 @@ export default function History({user}: {user: User}) {
module === "listening" && "bg-ielts-listening", module === "listening" && "bg-ielts-listening",
module === "writing" && "bg-ielts-writing", module === "writing" && "bg-ielts-writing",
module === "speaking" && "bg-ielts-speaking", module === "speaking" && "bg-ielts-speaking",
module === "level" && "bg-ielts-level", module === "level" && "bg-ielts-level"
)}> )}
>
{module === "reading" && <BsBook className="w-4 h-4" />} {module === "reading" && <BsBook className="w-4 h-4" />}
{module === "listening" && <BsHeadphones className="w-4 h-4" />} {module === "listening" && <BsHeadphones className="w-4 h-4" />}
{module === "writing" && <BsPen className="w-4 h-4" />} {module === "writing" && <BsPen className="w-4 h-4" />}
@@ -277,7 +333,8 @@ export default function History({user}: {user: User}) {
{assignment && ( {assignment && (
<span className="font-light text-sm"> <span className="font-light text-sm">
Assignment: {assignment.name}, Teacher: {users.find((u) => u.id === assignment.assigner)?.name} Assignment: {assignment.name}, Teacher:{" "}
{users.find((u) => u.id === assignment.assigner)?.name}
</span> </span>
)} )}
</div> </div>
@@ -292,12 +349,15 @@ export default function History({user}: {user: User}) {
"flex flex-col justify-between gap-4 border border-mti-gray-platinum p-4 cursor-pointer rounded-xl transition ease-in-out duration-300 -md:hidden", "flex flex-col justify-between gap-4 border border-mti-gray-platinum p-4 cursor-pointer rounded-xl transition ease-in-out duration-300 -md:hidden",
isDisabled && "grayscale tooltip", isDisabled && "grayscale tooltip",
correct / total >= 0.7 && "hover:border-mti-purple", correct / total >= 0.7 && "hover:border-mti-purple",
correct / total >= 0.3 && correct / total < 0.7 && "hover:border-mti-red", correct / total >= 0.3 &&
correct / total < 0.3 && "hover:border-mti-rose", correct / total < 0.7 &&
"hover:border-mti-red",
correct / total < 0.3 && "hover:border-mti-rose"
)} )}
onClick={isDisabled ? () => null : selectExam} onClick={isDisabled ? () => null : selectExam}
data-tip="This exam is still being evaluated..." data-tip="This exam is still being evaluated..."
role="button"> role="button"
>
{content} {content}
</div> </div>
<div <div
@@ -305,18 +365,86 @@ export default function History({user}: {user: User}) {
className={clsx( className={clsx(
"flex flex-col gap-4 border border-mti-gray-platinum p-4 cursor-pointer rounded-xl transition ease-in-out duration-300 -md:tooltip md:hidden", "flex flex-col gap-4 border border-mti-gray-platinum p-4 cursor-pointer rounded-xl transition ease-in-out duration-300 -md:tooltip md:hidden",
correct / total >= 0.7 && "hover:border-mti-purple", correct / total >= 0.7 && "hover:border-mti-purple",
correct / total >= 0.3 && correct / total < 0.7 && "hover:border-mti-red", correct / total >= 0.3 &&
correct / total < 0.3 && "hover:border-mti-rose", correct / total < 0.7 &&
"hover:border-mti-red",
correct / total < 0.3 && "hover:border-mti-rose"
)} )}
data-tip="Your screen size is too small to view previous exams." data-tip="Your screen size is too small to view previous exams."
role="button"> role="button"
>
{content} {content}
</div> </div>
</> </>
); );
}; };
const selectedUser = users.find((x) => x.id === statsUserId) || user; const selectableCorporates = [
defaultSelectableCorporate,
...users
.filter((x) => x.type === "corporate")
.map((x) => ({
value: x.id,
label: `${x.name} - ${x.email}`,
})),
];
const [selectedCorporate, setSelectedCorporate] = useState<string>(
defaultSelectableCorporate.value
);
const getUsersList = (): User[] => {
if (selectedCorporate) {
// get groups for that corporate
const selectedCorporateGroups = allGroups.filter(
(x) => x.admin === selectedCorporate
);
// get the teacher ids for that group
const selectedCorporateGroupsParticipants =
selectedCorporateGroups.flatMap((x) => x.participants);
// // search for groups for these teachers
// const teacherGroups = allGroups.filter((x) => {
// return selectedCorporateGroupsParticipants.includes(x.admin);
// });
// const usersList = [
// ...selectedCorporateGroupsParticipants,
// ...teacherGroups.flatMap((x) => x.participants),
// ];
const userListWithUsers = selectedCorporateGroupsParticipants.map((x) =>
users.find((y) => y.id === x)
) as User[];
return userListWithUsers.filter((x) => x);
}
return users || [];
};
const corporateFilteredUserList = getUsersList();
const getSelectedUser = () => {
if (selectedCorporate) {
const userInCorporate = corporateFilteredUserList.find(
(x) => x.id === statsUserId
);
return userInCorporate || corporateFilteredUserList[0];
}
return users.find((x) => x.id === statsUserId) || user;
};
const selectedUser = getSelectedUser();
const selectedUserSelectValue = selectedUser
? {
value: selectedUser.id,
label: `${selectedUser.name} - ${selectedUser.email}`,
}
: {
value: "",
label: "",
};
return ( return (
<> <>
<Head> <Head>
@@ -334,36 +462,90 @@ export default function History({user}: {user: User}) {
<div className="w-full flex -xl:flex-col -xl:gap-4 justify-between items-center"> <div className="w-full flex -xl:flex-col -xl:gap-4 justify-between items-center">
<div className="xl:w-3/4"> <div className="xl:w-3/4">
{(user.type === "developer" || user.type === "admin") && ( {(user.type === "developer" || user.type === "admin") && (
<>
<label className="font-normal text-base text-mti-gray-dim">
Corporate
</label>
<Select <Select
options={users.map((x) => ({value: x.id, label: `${x.name} - ${x.email}`}))} options={selectableCorporates}
value={{value: selectedUser.id, label: `${selectedUser.name} - ${selectedUser.email}`}} value={selectableCorporates.find(
(x) => x.value === selectedCorporate
)}
onChange={(value) =>
setSelectedCorporate(value?.value || "")
}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
></Select>
<label className="font-normal text-base text-mti-gray-dim">
User
</label>
<Select
options={corporateFilteredUserList.map((x) => ({
value: x.id,
label: `${x.name} - ${x.email}`,
}))}
value={selectedUserSelectValue}
onChange={(value) => setStatsUserId(value?.value)} onChange={(value) => setStatsUserId(value?.value)}
styles={{ styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }), menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (styles, state) => ({ option: (styles, state) => ({
...styles, ...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", backgroundColor: state.isFocused
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color, color: state.isFocused ? "black" : styles.color,
}), }),
}} }}
/> />
</>
)} )}
{(user.type === "corporate" || user.type === "teacher") && groups.length > 0 && ( {(user.type === "corporate" || user.type === "teacher") &&
groups.length > 0 && (
<>
<label className="font-normal text-base text-mti-gray-dim">
User
</label>
<Select <Select
options={users options={users
.filter((x) => groups.flatMap((y) => y.participants).includes(x.id)) .filter((x) =>
.map((x) => ({value: x.id, label: `${x.name} - ${x.email}`}))} groups.flatMap((y) => y.participants).includes(x.id)
value={{value: selectedUser.id, label: `${selectedUser.name} - ${selectedUser.email}`}} )
.map((x) => ({
value: x.id,
label: `${x.name} - ${x.email}`,
}))}
value={selectedUserSelectValue}
onChange={(value) => setStatsUserId(value?.value)} onChange={(value) => setStatsUserId(value?.value)}
styles={{ styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }), menuPortal: (base) => ({ ...base, zIndex: 9999 }),
option: (styles, state) => ({ option: (styles, state) => ({
...styles, ...styles,
backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", backgroundColor: state.isFocused
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color, color: state.isFocused ? "black" : styles.color,
}), }),
}} }}
/> />
</>
)} )}
</div> </div>
<div className="flex gap-4 w-full justify-center xl:justify-end"> <div className="flex gap-4 w-full justify-center xl:justify-end">
@@ -371,49 +553,59 @@ export default function History({user}: {user: User}) {
className={clsx( className={clsx(
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light", "bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
"transition duration-300 ease-in-out", "transition duration-300 ease-in-out",
filter === "assignments" && "!bg-mti-purple-light !text-white", filter === "assignments" && "!bg-mti-purple-light !text-white"
)} )}
onClick={() => toggleFilter("assignments")}> onClick={() => toggleFilter("assignments")}
>
Assignments Assignments
</button> </button>
<button <button
className={clsx( className={clsx(
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light", "bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
"transition duration-300 ease-in-out", "transition duration-300 ease-in-out",
filter === "months" && "!bg-mti-purple-light !text-white", filter === "months" && "!bg-mti-purple-light !text-white"
)} )}
onClick={() => toggleFilter("months")}> onClick={() => toggleFilter("months")}
>
Last month Last month
</button> </button>
<button <button
className={clsx( className={clsx(
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light", "bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
"transition duration-300 ease-in-out", "transition duration-300 ease-in-out",
filter === "weeks" && "!bg-mti-purple-light !text-white", filter === "weeks" && "!bg-mti-purple-light !text-white"
)} )}
onClick={() => toggleFilter("weeks")}> onClick={() => toggleFilter("weeks")}
>
Last week Last week
</button> </button>
<button <button
className={clsx( className={clsx(
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light", "bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
"transition duration-300 ease-in-out", "transition duration-300 ease-in-out",
filter === "days" && "!bg-mti-purple-light !text-white", filter === "days" && "!bg-mti-purple-light !text-white"
)} )}
onClick={() => toggleFilter("days")}> onClick={() => toggleFilter("days")}
>
Last day Last day
</button> </button>
</div> </div>
</div> </div>
{groupedStats && Object.keys(groupedStats).length > 0 && !isStatsLoading && ( {groupedStats &&
Object.keys(groupedStats).length > 0 &&
!isStatsLoading && (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 w-full gap-4 xl:gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 w-full gap-4 xl:gap-6">
{Object.keys(filterStatsByDate(groupedStats)) {Object.keys(filterStatsByDate(groupedStats))
.sort((a, b) => parseInt(b) - parseInt(a)) .sort((a, b) => parseInt(b) - parseInt(a))
.map(customContent)} .map(customContent)}
</div> </div>
)} )}
{groupedStats && Object.keys(groupedStats).length === 0 && !isStatsLoading && ( {groupedStats &&
<span className="font-semibold ml-1">No record to display...</span> Object.keys(groupedStats).length === 0 &&
!isStatsLoading && (
<span className="font-semibold ml-1">
No record to display...
</span>
)} )}
</Layout> </Layout>
)} )}