diff --git a/src/components/DemographicInformationInput.tsx b/src/components/DemographicInformationInput.tsx index ce647f4c..3f32eb49 100644 --- a/src/components/DemographicInformationInput.tsx +++ b/src/components/DemographicInformationInput.tsx @@ -17,7 +17,7 @@ import moment from "moment"; interface Props { user: User; - mutateUser: KeyedMutator; + mutateUser: (user: User) => void; } export default function DemographicInformationInput({user, mutateUser}: Props) { diff --git a/src/dashboards/Admin.tsx b/src/dashboards/Admin.tsx index e3bc48ce..edb7d165 100644 --- a/src/dashboards/Admin.tsx +++ b/src/dashboards/Admin.tsx @@ -31,12 +31,40 @@ interface Props { user: User; } +const studentHash = { + type: "student", + size: 25, + orderBy: "registrationDate", +}; + +const teacherHash = { + type: "teacher", + size: 25, + orderBy: "registrationDate", +}; + +const corporateHash = { + type: "corporate", + size: 25, + orderBy: "registrationDate", +}; + +const agentsHash = { + type: "agent", + size: 25, + orderBy: "registrationDate", +}; + export default function AdminDashboard({user}: Props) { const [page, setPage] = useState(""); const [selectedUser, setSelectedUser] = useState(); const [showModal, setShowModal] = useState(false); - const {users, reload, isLoading} = useUsers(); + const {users: students, total: totalStudents, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers(studentHash); + const {users: teachers, total: totalTeachers, reload: reloadTeachers, isLoading: isTeachersLoading} = useUsers(teacherHash); + const {users: corporates, total: totalCorporate, reload: reloadCorporates, isLoading: isCorporatesLoading} = useUsers(corporateHash); + const {users: agents, total: totalAgents, reload: reloadAgents, isLoading: isAgentsLoading} = useUsers(agentsHash); + const {groups} = useGroups({}); const {pending, done} = usePaymentStatusUsers(); @@ -47,9 +75,6 @@ export default function AdminDashboard({user}: Props) { setShowModal(!!selectedUser && router.asPath === "/#"); }, [selectedUser, router.asPath]); - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(reload, [page]); - const inactiveCountryManagerFilter = (x: User) => x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate); const UserDisplay = (displayUser: User) => ( @@ -279,50 +304,50 @@ export default function AdminDashboard({user}: Props) {
x.type === "student").length} + value={totalStudents} onClick={() => router.push("/#students")} color="purple" /> x.type === "teacher").length} + value={totalTeachers} onClick={() => router.push("/#teachers")} color="purple" /> x.type === "corporate").length} + value={totalCorporate} onClick={() => router.push("/#corporate")} color="purple" /> x.type === "agent").length} + value={totalAgents} onClick={() => router.push("/#agents")} color="purple" /> x.demographicInformation).map((x) => x.demographicInformation?.country))].length} + value={[...new Set(agents.filter((x) => x.demographicInformation).map((x) => x.demographicInformation?.country))].length} color="purple" /> router.push("/#inactiveStudents")} Icon={BsPersonFill} - isLoading={isLoading} + isLoading={isStudentsLoading} label="Inactive Students" value={ - users.filter((x) => x.type === "student" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate))) + students.filter((x) => x.type === "student" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate))) .length } color="rose" @@ -330,26 +355,26 @@ export default function AdminDashboard({user}: Props) { router.push("/#inactiveCountryManagers")} Icon={BsBriefcaseFill} - isLoading={isLoading} + isLoading={isAgentsLoading} label="Inactive Country Managers" - value={users.filter(inactiveCountryManagerFilter).length} + value={agents.filter(inactiveCountryManagerFilter).length} color="rose" /> router.push("/#inactiveCorporate")} Icon={BsBank} - isLoading={isLoading} + isLoading={isCorporatesLoading} label="Inactive Corporate" value={ - users.filter((x) => x.type === "corporate" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate))) - .length + corporates.filter( + (x) => x.type === "corporate" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate)), + ).length } color="rose" /> router.push("/#paymentdone")} Icon={BsCurrencyDollar} - isLoading={isLoading} label="Payment Done" value={done.length} color="purple" @@ -357,7 +382,6 @@ export default function AdminDashboard({user}: Props) { router.push("/#paymentpending")} Icon={BsCurrencyDollar} - isLoading={isLoading} label="Pending Payment" value={pending.length} color="rose" @@ -365,14 +389,13 @@ export default function AdminDashboard({user}: Props) { router.push("https://cms.encoach.com/admin")} Icon={BsLayoutSidebar} - isLoading={isLoading} label="Content Management System (CMS)" color="green" /> router.push("/#corporatestudentslevels")} Icon={BsPersonFill} - isLoading={isLoading} + isLoading={isStudentsLoading} label="Corporate Students Levels" color="purple" /> @@ -382,8 +405,7 @@ export default function AdminDashboard({user}: Props) {
Latest students
- {users - .filter((x) => x.type === "student") + {students .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) .map((x) => ( @@ -393,8 +415,7 @@ export default function AdminDashboard({user}: Props) {
Latest teachers
- {users - .filter((x) => x.type === "teacher") + {teachers .sort((a, b) => { return dateSorter(a, b, "desc", "registrationDate"); }) @@ -406,8 +427,7 @@ export default function AdminDashboard({user}: Props) {
Latest corporate
- {users - .filter((x) => x.type === "corporate") + {corporates .sort((a, b) => { return dateSorter(a, b, "desc", "registrationDate"); }) @@ -419,8 +439,8 @@ export default function AdminDashboard({user}: Props) {
Unpaid Corporate
- {users - .filter((x) => x.type === "corporate" && x.status === "paymentDue") + {corporates + .filter((x) => x.status === "paymentDue") .map((x) => ( ))} @@ -429,10 +449,9 @@ export default function AdminDashboard({user}: Props) {
Students expiring in 1 month
- {users + {students .filter( (x) => - x.type === "student" && x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate).subtract(30, "days")) && moment().isBefore(moment(x.subscriptionExpirationDate)), @@ -445,10 +464,9 @@ export default function AdminDashboard({user}: Props) {
Teachers expiring in 1 month
- {users + {teachers .filter( (x) => - x.type === "teacher" && x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate).subtract(30, "days")) && moment().isBefore(moment(x.subscriptionExpirationDate)), @@ -461,10 +479,9 @@ export default function AdminDashboard({user}: Props) {
Country Manager expiring in 1 month
- {users + {agents .filter( (x) => - x.type === "agent" && x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate).subtract(30, "days")) && moment().isBefore(moment(x.subscriptionExpirationDate)), @@ -477,10 +494,9 @@ export default function AdminDashboard({user}: Props) {
Corporate expiring in 1 month
- {users + {corporates .filter( (x) => - x.type === "corporate" && x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate).subtract(30, "days")) && moment().isBefore(moment(x.subscriptionExpirationDate)), @@ -493,10 +509,8 @@ export default function AdminDashboard({user}: Props) {
Expired Students
- {users - .filter( - (x) => x.type === "student" && x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate)), - ) + {students + .filter((x) => x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate))) .map((x) => ( ))} @@ -505,10 +519,8 @@ export default function AdminDashboard({user}: Props) {
Expired Teachers
- {users - .filter( - (x) => x.type === "teacher" && x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate)), - ) + {teachers + .filter((x) => x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate))) .map((x) => ( ))} @@ -517,10 +529,8 @@ export default function AdminDashboard({user}: Props) {
Expired Country Manager
- {users - .filter( - (x) => x.type === "agent" && x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate)), - ) + {agents + .filter((x) => x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate))) .map((x) => ( ))} @@ -529,11 +539,8 @@ export default function AdminDashboard({user}: Props) {
Expired Corporate
- {users - .filter( - (x) => - x.type === "corporate" && x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate)), - ) + {corporates + .filter((x) => x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate))) .map((x) => ( ))} @@ -553,7 +560,10 @@ export default function AdminDashboard({user}: Props) { loggedInUser={user} onClose={(shouldReload) => { setSelectedUser(undefined); - if (shouldReload) reload(); + if (shouldReload && selectedUser!.type === "student") reloadStudents(); + if (shouldReload && selectedUser!.type === "teacher") reloadTeachers(); + if (shouldReload && selectedUser!.type === "corporate") reloadCorporates(); + if (shouldReload && selectedUser!.type === "agent") reloadAgents(); }} onViewStudents={ selectedUser.type === "corporate" || selectedUser.type === "teacher" diff --git a/src/dashboards/MasterCorporate/MasterStatistical.tsx b/src/dashboards/MasterCorporate/MasterStatistical.tsx index 46291bec..17d0708c 100644 --- a/src/dashboards/MasterCorporate/MasterStatistical.tsx +++ b/src/dashboards/MasterCorporate/MasterStatistical.tsx @@ -59,7 +59,6 @@ const MasterStatistical = (props: Props) => { const [endDate, setEndDate] = React.useState(moment().endOf("year").toDate()); const {assignments} = useAssignmentsCorporates({ - // corporates: [...corporates, "tYU0HTiJdjMsS8SB7XJsUdMMP892"], corporates: selectedCorporates, startDate, endDate, @@ -69,38 +68,40 @@ const MasterStatistical = (props: Props) => { const tableResults = React.useMemo( () => - assignments.reduce((accmA: TableData[], a: AssignmentWithCorporateId) => { - const userResults = a.assignees.map((assignee) => { - const userStats = a.results.find((r) => r.user === assignee)?.stats || []; - const userData = users.find((u) => u.id === assignee); - const corporate = users.find((u) => u.id === a.assigner)?.name || ""; - const commonData = { - user: userData?.name || "", - email: userData?.email || "", - userId: assignee, - corporateId: a.corporateId, - corporate, - assignment: a.name, - }; - if (userStats.length === 0) { + assignments + .reduce((accmA: TableData[], a: AssignmentWithCorporateId) => { + const userResults = a.assignees.map((assignee) => { + const userStats = a.results.find((r) => r.user === assignee)?.stats || []; + const userData = users.find((u) => u.id === assignee); + const corporate = users.find((u) => u.id === a.assigner)?.name || ""; + const commonData = { + user: userData?.name || "N/A", + email: userData?.email || "N/A", + userId: assignee, + corporateId: a.corporateId, + corporate, + assignment: a.name, + }; + if (userStats.length === 0) { + return { + ...commonData, + correct: 0, + submitted: false, + // date: moment(), + }; + } + return { ...commonData, - correct: 0, - submitted: false, - // date: moment(), + correct: userStats.reduce((n, e) => n + e.score.correct, 0), + submitted: true, + date: moment.max(userStats.map((e) => moment(e.date))), }; - } + }) as TableData[]; - return { - ...commonData, - correct: userStats.reduce((n, e) => n + e.score.correct, 0), - submitted: true, - date: moment.max(userStats.map((e) => moment(e.date))), - }; - }) as TableData[]; - - return [...accmA, ...userResults]; - }, []), + return [...accmA, ...userResults]; + }, []) + .filter((x) => x.user !== "N/A"), [assignments, users], ); diff --git a/src/hooks/useSessions.tsx b/src/hooks/useSessions.tsx index 8b6e7b33..aa7ee4a9 100644 --- a/src/hooks/useSessions.tsx +++ b/src/hooks/useSessions.tsx @@ -1,12 +1,9 @@ import {Exam} from "@/interfaces/exam"; import {ExamState} from "@/stores/examStore"; -import Axios from "axios"; +import axios from "axios"; import {setupCache} from "axios-cache-interceptor"; import {useEffect, useState} from "react"; -const instance = Axios.create(); -const axios = setupCache(instance); - export type Session = ExamState & {user: string; id: string; date: string}; export default function useSessions(user?: string) { diff --git a/src/pages/(admin)/BatchCreateUser.tsx b/src/pages/(admin)/BatchCreateUser.tsx index 5f31c7c3..8b97d6a2 100644 --- a/src/pages/(admin)/BatchCreateUser.tsx +++ b/src/pages/(admin)/BatchCreateUser.tsx @@ -53,7 +53,7 @@ const USER_TYPE_PERMISSIONS: { }, admin: { perm: "createCodeAdmin", - list: ["student", "teacher", "agent", "corporate", "admin", "mastercorporate"], + list: ["student", "teacher", "agent", "corporate", "mastercorporate"], }, developer: { perm: undefined, @@ -161,7 +161,7 @@ export default function BatchCreateUser({user, users, permissions, onFinish}: Pr setIsLoading(true); try { - await axios.post("/api/batch_users", { users: newUsers.map(user => ({...user, type, expiryDate})) }); + await axios.post("/api/batch_users", {users: newUsers.map((user) => ({...user, type, expiryDate}))}); toast.success(`Successfully added ${newUsers.length} user(s)!`); onFinish(); } catch { diff --git a/src/pages/(admin)/Lists/UserList.tsx b/src/pages/(admin)/Lists/UserList.tsx index bcb3dceb..9b0ea9df 100644 --- a/src/pages/(admin)/Lists/UserList.tsx +++ b/src/pages/(admin)/Lists/UserList.tsx @@ -32,7 +32,11 @@ import Input from "@/components/Low/Input"; const columnHelper = createColumnHelper(); const searchFields = [["name"], ["email"], ["corporateInformation", "companyInformation", "name"]]; -const CompanyNameCell = ({ users, user, groups }: { user: User; users: User[]; groups: Group[] }) => { +const corporatesHash = { + type: "corporate", +}; + +const CompanyNameCell = ({users, user, groups}: {user: User; users: User[]; groups: Group[]}) => { const [companyName, setCompanyName] = useState(""); const [isLoading, setIsLoading] = useState(false); @@ -64,9 +68,13 @@ export default function UserList({ const { users, total, isLoading, reload } = useUsers({type: type, size: 16, page: page, searchTerm: searchTerm}); - const { permissions } = usePermissions(user?.id || ""); - const { balance } = useUserBalance(); - const { groups } = useGroups({ + const {users: corporates} = useUsers(corporatesHash); + + const totalUsers = useMemo(() => [...users, ...corporates], [users, corporates]); + + const {permissions} = usePermissions(user?.id || ""); + const {balance} = useUserBalance(); + const {groups} = useGroups({ admin: user && ["corporate", "teacher", "mastercorporate"].includes(user?.type) ? user.id : undefined, userType: user?.type, }); @@ -346,7 +354,7 @@ export default function UserList({ ) as any, - cell: (info) => , + cell: (info) => , }), columnHelper.accessor("subscriptionExpirationDate", { header: ( diff --git a/src/pages/(admin)/UserCreator.tsx b/src/pages/(admin)/UserCreator.tsx index 12415726..03fc35e6 100644 --- a/src/pages/(admin)/UserCreator.tsx +++ b/src/pages/(admin)/UserCreator.tsx @@ -141,7 +141,11 @@ export default function UserCreator({user, users, permissions, onFinish}: Props) setType("student"); setPosition(undefined); }) - .catch(() => toast.error("Something went wrong! Please try again later!")) + .catch((error) => { + const data = error?.response?.data; + if (!!data?.message) return toast.error(data.message); + toast.error("Something went wrong! Please try again later!"); + }) .finally(() => setIsLoading(false)); }; diff --git a/src/pages/api/make_user.ts b/src/pages/api/make_user.ts index 31b0e69d..b8128015 100644 --- a/src/pages/api/make_user.ts +++ b/src/pages/api/make_user.ts @@ -218,6 +218,8 @@ async function post(req: NextApiRequest, res: NextApiResponse) { return res.status(200).json({ok: true}); }) .catch((error) => { + if (error.code.includes("email-already-in-use")) return res.status(403).json({error, message: "E-mail is already in the platform."}); + console.log(`Failing - ${email}`); console.log(error); return res.status(401).json({error}); diff --git a/src/pages/api/sessions/index.ts b/src/pages/api/sessions/index.ts index 1e7c83f1..267ae57b 100644 --- a/src/pages/api/sessions/index.ts +++ b/src/pages/api/sessions/index.ts @@ -24,7 +24,7 @@ async function get(req: NextApiRequest, res: NextApiResponse) { const {user} = req.query as {user?: string}; const q = user ? {user: user} : {}; - const sessions = await db.collection("sessions").find(q).toArray(); + const sessions = await db.collection("sessions").find(q).limit(10).toArray(); res.status(200).json( sessions.filter((x) => { @@ -41,12 +41,8 @@ async function post(req: NextApiRequest, res: NextApiResponse) { return; } const session = req.body; - - await db.collection("sessions").updateOne( - { id: session.id}, - { $set: session }, - { upsert: true } - ); + + await db.collection("sessions").updateOne({id: session.id}, {$set: session}, {upsert: true}); res.status(200).json({ok: true}); } diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 153bb52c..6a4f9ca4 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -68,18 +68,18 @@ interface Props { linkedCorporate?: CorporateUser | MasterCorporateUser; } -export default function Home({linkedCorporate}: Props) { +export default function Home({user: propsUser, linkedCorporate}: Props) { + const [user, setUser] = useState(propsUser); const [showDiagnostics, setShowDiagnostics] = useState(false); const [showDemographicInput, setShowDemographicInput] = useState(false); const [selectedScreen, setSelectedScreen] = useState("admin"); - const {user, mutateUser} = useUser({redirectTo: "/login"}); + const {mutateUser} = useUser({redirectTo: "/login"}); const router = useRouter(); useEffect(() => { if (user) { - console.log(user.demographicInformation); - setShowDemographicInput(!user.demographicInformation || !user.demographicInformation.country || !user.demographicInformation.phone); + // setShowDemographicInput(!user.demographicInformation || !user.demographicInformation.country || !user.demographicInformation.phone); setShowDiagnostics(user.isFirstLogin && user.type === "student"); } }, [user]); @@ -131,7 +131,13 @@ export default function Home({linkedCorporate}: Props) { - + { + setUser(user); + mutateUser(user); + }} + user={user} + /> ); diff --git a/src/utils/exams.be.ts b/src/utils/exams.be.ts index 18621bc6..8b7b83df 100644 --- a/src/utils/exams.be.ts +++ b/src/utils/exams.be.ts @@ -5,7 +5,7 @@ import {DeveloperUser, Stat, StudentUser, User} from "@/interfaces/user"; import {Module} from "@/interfaces"; import {getCorporateUser} from "@/resources/user"; import {getUserCorporate} from "./groups.be"; -import { Db, ObjectId } from "mongodb"; +import {Db, ObjectId} from "mongodb"; export const getExams = async ( db: Db, @@ -18,18 +18,18 @@ export const getExams = async ( variant?: Variant, instructorGender?: InstructorGender, ): Promise => { + const allExams = await db + .collection(module) + .find({ + isDiagnostic: false, + }) + .toArray(); - const allExams = await db.collection(module).find({ - isDiagnostic: false - }).toArray(); - - const shuffledPublicExams = ( - shuffle( - allExams.map((doc) => ({ - ...doc, - module, - })) as Exam[], - ) + const shuffledPublicExams = shuffle( + allExams.map((doc) => ({ + ...doc, + module, + })) as Exam[], ).filter((x) => !x.private); let exams: Exam[] = await filterByOwners(shuffledPublicExams, userId); @@ -39,9 +39,12 @@ export const getExams = async ( exams = await filterByPreference(db, exams, module, userId); if (avoidRepeated === "true") { - const stats = await db.collection("stats").find({ - user: userId - }).toArray(); + const stats = await db + .collection("stats") + .find({ + user: userId, + }) + .toArray(); const filteredExams = exams.filter((x) => !stats.map((s) => s.exam).includes(x.id)); @@ -78,7 +81,7 @@ const filterByOwners = async (exams: Exam[], userID?: string) => { const filterByDifficulty = async (db: Db, exams: Exam[], module: Module, userID?: string) => { if (!userID) return exams; - const user = await db.collection("users").findOne({ _id: new ObjectId(userID) }); + const user = await db.collection("users").findOne({id: userID}); if (!user) return exams; const difficulty = user.levels[module] <= 3 ? "easy" : user.levels[module] <= 6 ? "medium" : "hard"; @@ -92,7 +95,7 @@ const filterByPreference = async (db: Db, exams: Exam[], module: Module, userID? if (!userID) return exams; - const user = await db.collection("users").findOne({ _id: new ObjectId(userID) }); + const user = await db.collection("users").findOne({id: userID}); if (!user) return exams; if (!["developer", "student"].includes(user.type)) return exams; diff --git a/src/utils/users.be.ts b/src/utils/users.be.ts index 57de4924..1b8c8491 100644 --- a/src/utils/users.be.ts +++ b/src/utils/users.be.ts @@ -67,7 +67,7 @@ export async function getLinkedUsers( const participants = uniq([ ...adminGroups.flatMap((x) => x.participants), - ...groups.flat().flatMap((x) => x.participants), + ...(userType === "mastercorporate" ? groups.flat().flatMap((x) => x.participants) : []), ...(userType === "teacher" ? belongingGroups.flatMap((x) => x.participants) : []), ]);