Merged develop into ENCOA-83_MasterStatistical

This commit is contained in:
João Ramos
2024-08-20 23:39:57 +00:00
28 changed files with 5976 additions and 3637 deletions

View File

@@ -14,7 +14,7 @@ import {toast} from "react-toastify";
import readXlsxFile from "read-excel-file";
import {useFilePicker} from "use-file-picker";
import {getUserCorporate} from "@/utils/groups";
import {isAgentUser, isCorporateUser} from "@/resources/user";
import {isAgentUser, isCorporateUser, USER_TYPE_LABELS} from "@/resources/user";
import {checkAccess} from "@/utils/permissions";
import usePermissions from "@/hooks/usePermissions";
@@ -109,7 +109,7 @@ const CreatePanel = ({user, users, group, onClose}: CreateDialogProps) => {
const submit = () => {
setIsLoading(true);
if (name !== group?.name && (name === "Students" || name === "Teachers")) {
if (name !== group?.name && (name?.trim() === "Students" || name?.trim() === "Teachers" || name?.trim() === "Corporate")) {
toast.error("That group name is reserved and cannot be used, please enter another one.");
setIsLoading(false);
return;
@@ -202,7 +202,6 @@ const filterTypes = ["corporate", "teacher", "mastercorporate"];
export default function GroupList({user}: {user: User}) {
const [isCreating, setIsCreating] = useState(false);
const [editingGroup, setEditingGroup] = useState<Group>();
const [filterByUser, setFilterByUser] = useState(false);
const {permissions} = usePermissions(user?.id || "");
@@ -218,12 +217,6 @@ export default function GroupList({user}: {user: User}) {
adminAdmins: user?.id,
});
useEffect(() => {
if (user && ["corporate", "teacher", "mastercorporate"].includes(user.type)) {
setFilterByUser(true);
}
}, [user]);
const deleteGroup = (group: Group) => {
if (!confirm(`Are you sure you want to delete "${group.name}"?`)) return;
@@ -246,7 +239,7 @@ export default function GroupList({user}: {user: User}) {
columnHelper.accessor("admin", {
header: "Admin",
cell: (info) => (
<div className="tooltip" data-tip={capitalize(users.find((x) => x.id === info.getValue())?.type)}>
<div className="tooltip" data-tip={USER_TYPE_LABELS[users.find((x) => x.id === info.getValue())?.type || "student"]}>
{users.find((x) => x.id === info.getValue())?.name}
</div>
),
@@ -309,7 +302,7 @@ export default function GroupList({user}: {user: User}) {
user={user}
onClose={closeModal}
users={
user?.type === "corporate" || user?.type === "teacher"
checkAccess(user, ["corporate", "teacher", "mastercorporate"])
? users.filter(
(u) =>
groups

View File

@@ -58,7 +58,10 @@ export default function UserList({
const {users, reload} = useUsers();
const {permissions} = usePermissions(user?.id || "");
const {groups} = useGroups({admin: user && ["corporate", "teacher", "mastercorporate"].includes(user?.type) ? user.id : undefined});
const {groups} = useGroups({
admin: user && ["corporate", "teacher", "mastercorporate"].includes(user?.type) ? user.id : undefined,
userType: user?.type,
});
const appendUserFilters = useFilterStore((state) => state.appendUserFilter);
const router = useRouter();
@@ -76,10 +79,9 @@ export default function UserList({
useEffect(() => {
(async () => {
if (user && users) {
const filterUsers =
user.type === "corporate" || user.type === "teacher"
? users.filter((u) => groups.flatMap((g) => g.participants).includes(u.id))
: users;
const filterUsers = ["corporate", "teacher", "mastercorporate"].includes(user.type)
? users.filter((u) => groups.flatMap((g) => g.participants).includes(u.id))
: users;
const filteredUsers = filters.reduce((d, f) => d.filter(f), filterUsers);
const sortedUsers = await asyncSorter<User>(filteredUsers, sortFunction);

View File

@@ -23,6 +23,7 @@ import {toast, ToastContainer} from "react-toastify";
import {v4 as uuidv4} from "uuid";
import useSessions from "@/hooks/useSessions";
import ShortUniqueId from "short-unique-id";
import clsx from "clsx";
interface Props {
page: "exams" | "exercises";
@@ -54,6 +55,7 @@ export default function ExamPage({page}: Props) {
const {showSolutions, setShowSolutions} = useExamStore((state) => state);
const {selectedModules, setSelectedModules} = useExamStore((state) => state);
const {inactivity, setInactivity} = useExamStore((state) => state);
const {bgColor, setBgColor} = useExamStore((state) => state);
const {user} = useUser({redirectTo: "/login"});
const router = useRouter();
@@ -280,6 +282,13 @@ export default function ExamPage({page}: Props) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [statsAwaitingEvaluation]);
useEffect(()=> {
if(exam && exam.module === "level" && exam.parts[0].intro && !showSolutions) {
setBgColor("bg-ielts-level-light");
}
}, [exam, showSolutions, setBgColor])
const checkIfStatsHaveBeenEvaluated = (ids: string[]) => {
setTimeout(async () => {
try {
@@ -513,6 +522,7 @@ export default function ExamPage({page}: Props) {
{user && (
<Layout
user={user}
bgColor={bgColor}
className="justify-between"
focusMode={selectedModules.length !== 0 && !showSolutions && moduleIndex < selectedModules.length}
onFocusLayerMouseEnter={() => setShowAbandonPopup(true)}>

View File

@@ -1,94 +1,68 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next";
import { app } from "@/firebase";
import {
getFirestore,
collection,
getDocs,
setDoc,
doc,
query,
where,
} from "firebase/firestore";
import { withIronSessionApiRoute } from "iron-session/next";
import { sessionOptions } from "@/lib/session";
import { Group } from "@/interfaces/user";
import { v4 } from "uuid";
import { updateExpiryDateOnGroup, getGroupsForUser } from "@/utils/groups.be";
import type {NextApiRequest, NextApiResponse} from "next";
import {app} from "@/firebase";
import {getFirestore, collection, getDocs, setDoc, doc, query, where} from "firebase/firestore";
import {withIronSessionApiRoute} from "iron-session/next";
import {sessionOptions} from "@/lib/session";
import {Group} from "@/interfaces/user";
import {v4} from "uuid";
import {updateExpiryDateOnGroup, getGroupsForUser} from "@/utils/groups.be";
import {uniqBy} from "lodash";
const db = getFirestore(app);
export default withIronSessionApiRoute(handler, sessionOptions);
async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!req.session.user) {
res.status(401).json({ ok: false });
return;
}
if (!req.session.user) {
res.status(401).json({ok: false});
return;
}
if (req.method === "GET") await get(req, res);
if (req.method === "POST") await post(req, res);
if (req.method === "GET") await get(req, res);
if (req.method === "POST") await post(req, res);
}
async function get(req: NextApiRequest, res: NextApiResponse) {
const { admin, participant } = req.query as {
admin: string;
participant: string;
};
const {admin, participant} = req.query as {
admin: string;
participant: string;
};
if (req.session?.user?.type === "mastercorporate") {
try {
const masterCorporateGroups = await getGroupsForUser(admin, participant);
const corporatesFromMaster = masterCorporateGroups
.filter((g) => g.name === "Corporate")
.flatMap((g) => g.participants);
if (req.session?.user?.type === "mastercorporate") {
try {
const masterCorporateGroups = await getGroupsForUser(admin, participant);
const corporatesFromMaster = masterCorporateGroups.filter((g) => g.name.trim() === "Corporate").flatMap((g) => g.participants);
if (corporatesFromMaster.length === 0) {
res.status(200).json([]);
return;
}
Promise.all(
corporatesFromMaster.map((c) => getGroupsForUser(c, participant))
)
.then((groups) => {
res.status(200).json([...masterCorporateGroups, ...groups.flat()]);
return;
})
.catch((e) => {
console.error(e);
res.status(500).json({ ok: false });
return;
});
} catch (e) {
console.error(e);
res.status(500).json({ ok: false });
return;
}
return;
}
if (corporatesFromMaster.length === 0) return res.status(200).json(masterCorporateGroups);
try {
const groups = await getGroupsForUser(admin, participant);
res.status(200).json(groups);
} catch (e) {
console.error(e);
res.status(500).json({ ok: false });
}
const groups = await Promise.all(corporatesFromMaster.map((c) => getGroupsForUser(c, participant)));
return res.status(200).json([...masterCorporateGroups, ...uniqBy(groups.flat(), "id")]);
} catch (e) {
console.error(e);
res.status(500).json({ok: false});
return;
}
}
try {
const groups = await getGroupsForUser(admin, participant);
res.status(200).json(groups);
} catch (e) {
console.error(e);
res.status(500).json({ok: false});
}
}
async function post(req: NextApiRequest, res: NextApiResponse) {
const body = req.body as Group;
const body = req.body as Group;
await Promise.all(
body.participants.map(
async (p) => await updateExpiryDateOnGroup(p, body.admin)
)
);
await Promise.all(body.participants.map(async (p) => await updateExpiryDateOnGroup(p, body.admin)));
await setDoc(doc(db, "groups", v4()), {
name: body.name,
admin: body.admin,
participants: body.participants,
});
res.status(200).json({ ok: true });
await setDoc(doc(db, "groups", v4()), {
name: body.name,
admin: body.admin,
participants: body.participants,
});
res.status(200).json({ok: true});
}

View File

@@ -61,13 +61,14 @@ export default function History({user}: {user: User}) {
state.training,
state.setTraining,
]);
// const [statsUserId, setStatsUserId] = useState<string | undefined>(user.id);
const [groupedStats, setGroupedStats] = useState<{[key: string]: Stat[]}>();
const [filter, setFilter] = useState<"months" | "weeks" | "days" | "assignments">();
const {assignments} = useAssignments({});
const {users} = useUsers();
const {stats, isLoading: isStatsLoading} = useStats(statsUserId);
const {stats, isLoading: isStatsLoading} = useStats(user?.type === "student" ? user?.id : statsUserId);
const {groups: allGroups} = useGroups({});
const groups = allGroups.filter((x) => x.admin === user.id);