ENCOA-316 ENCOA-317:

Refactor components to remove Layout wrapper and pass it in the App component , implemented a skeleton feedback while loading page and improved API calls related to Dashboard/User Profile
This commit is contained in:
José Marques Lima
2025-01-25 19:38:29 +00:00
parent 4d788e13b4
commit 37216e2a5a
56 changed files with 4440 additions and 2979 deletions

View File

@@ -1,189 +1,214 @@
/* eslint-disable @next/next/no-img-element */
import Layout from "@/components/High/Layout";
import UserDisplayList from "@/components/UserDisplayList";
import IconCard from "@/components/IconCard";
import { EntityWithRoles } from "@/interfaces/entity";
import { Assignment } from "@/interfaces/results";
import { Group, Stat, StudentUser, Type, User } from "@/interfaces/user";
import { Stat, Type, User } from "@/interfaces/user";
import { sessionOptions } from "@/lib/session";
import { dateSorter, filterBy, mapBy, redirect, serialize } from "@/utils";
import { filterBy, mapBy, redirect, serialize } from "@/utils";
import { requestUser } from "@/utils/api";
import { countEntitiesAssignments, getAssignments } from "@/utils/assignments.be";
import { getEntitiesWithRoles } from "@/utils/entities.be";
import { countGroups, getGroups } from "@/utils/groups.be";
import {
countEntitiesAssignments,
} from "@/utils/assignments.be";
import { getEntities } from "@/utils/entities.be";
import { countGroups } from "@/utils/groups.be";
import { checkAccess } from "@/utils/permissions";
import { calculateAverageLevel, calculateBandScore } from "@/utils/score";
import { groupByExam } from "@/utils/stats";
import { getStatsByUsers } from "@/utils/stats.be";
import { countUsers, getUser, getUsers } from "@/utils/users.be";
import {
countUsersByTypes,
getUsers,
} from "@/utils/users.be";
import { withIronSessionSsr } from "iron-session/next";
import { uniqBy } from "lodash";
import Head from "next/head";
import { useRouter } from "next/router";
import { useMemo } from "react";
import {
BsBank,
BsClipboard2Data,
BsEnvelopePaper,
BsPencilSquare,
BsPeople,
BsPeopleFill,
BsPersonFill,
BsPersonFillGear,
BsBank,
BsEnvelopePaper,
BsPencilSquare,
BsPeople,
BsPeopleFill,
BsPersonFill,
BsPersonFillGear,
} from "react-icons/bs";
import { ToastContainer } from "react-toastify";
interface Props {
user: User;
students: User[];
latestStudents: User[]
latestTeachers: User[]
entities: EntityWithRoles[];
usersCount: { [key in Type]: number }
assignmentsCount: number;
stats: Stat[];
groupsCount: number;
user: User;
students: User[];
latestStudents: User[];
latestTeachers: User[];
entities: EntityWithRoles[];
usersCount: { [key in Type]: number };
assignmentsCount: number;
stats: Stat[];
groupsCount: number;
}
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res)
if (!user || !user.isVerified) return redirect("/login")
const user = await requestUser(req, res);
if (!user || !user.isVerified) return redirect("/login");
if (!checkAccess(user, ["admin", "developer"])) return redirect("/")
if (!checkAccess(user, ["admin", "developer"])) return redirect("/");
const students = await getUsers({ type: 'student' }, 10, { averageLevel: -1 });
const usersCount = {
student: await countUsers({ type: "student" }),
teacher: await countUsers({ type: "teacher" }),
corporate: await countUsers({ type: "corporate" }),
mastercorporate: await countUsers({ type: "mastercorporate" }),
}
const students = await getUsers(
{ type: "student" },
10,
{
averageLevel: -1,
},
{ id: 1, name: 1, email: 1, profilePicture: 1 }
);
const latestStudents = await getUsers({ type: 'student' }, 10, { registrationDate: -1 })
const latestTeachers = await getUsers({ type: 'teacher' }, 10, { registrationDate: -1 })
const usersCount = await countUsersByTypes([
"student",
"teacher",
"corporate",
"mastercorporate",
]);
const entities = await getEntitiesWithRoles();
const assignmentsCount = await countEntitiesAssignments(mapBy(entities, 'id'), { archived: { $ne: true } });
const groupsCount = await countGroups();
const latestStudents = await getUsers(
{ type: "student" },
10,
{
registrationDate: -1,
},
{id:1, name: 1, email: 1, profilePicture: 1 }
);
const latestTeachers = await getUsers(
{ type: "teacher" },
10,
{
registrationDate: -1,
},
{ id:1,name: 1, email: 1, profilePicture: 1 }
);
return { props: serialize({ user, students, latestStudents, latestTeachers, usersCount, entities, assignmentsCount, groupsCount }) };
const entities = await getEntities(undefined, { _id: 0, id: 1, label: 1 });
const assignmentsCount = await countEntitiesAssignments(
mapBy(entities, "id"),
{ archived: { $ne: true } }
);
const groupsCount = await countGroups();
return {
props: serialize({
user,
students,
latestStudents,
latestTeachers,
usersCount,
entities,
assignmentsCount,
groupsCount,
}),
};
}, sessionOptions);
export default function Dashboard({
user,
students = [],
latestStudents,
latestTeachers,
usersCount,
entities,
assignmentsCount,
stats = [],
groupsCount
user,
students = [],
latestStudents,
latestTeachers,
usersCount,
entities,
assignmentsCount,
stats = [],
groupsCount,
}: Props) {
const router = useRouter();
const router = useRouter();
return (
<>
<Head>
<title>EnCoach</title>
<meta
name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<ToastContainer />
<Layout user={user}>
<section className="grid grid-cols-5 place-items-center -md:grid-cols-2 gap-4 text-center">
<IconCard
onClick={() => router.push("/users?type=student")}
Icon={BsPersonFill}
label="Students"
value={usersCount.student}
color="purple"
/>
<IconCard
onClick={() => router.push("/users?type=teacher")}
Icon={BsPencilSquare}
label="Teachers"
value={usersCount.teacher}
color="purple"
/>
<IconCard
Icon={BsBank}
onClick={() => router.push("/users?type=corporate")}
label="Corporates"
value={usersCount.corporate}
color="purple"
/>
<IconCard
Icon={BsBank}
onClick={() => router.push("/users?type=mastercorporate")}
label="Master Corporates"
value={usersCount.mastercorporate}
color="purple"
/>
<IconCard
Icon={BsPeople}
onClick={() => router.push("/classrooms")}
label="Classrooms"
value={groupsCount}
color="purple"
/>
<IconCard Icon={BsPeopleFill}
onClick={() => router.push("/entities")}
label="Entities"
value={entities.length}
color="purple"
/>
<IconCard Icon={BsPersonFillGear}
onClick={() => router.push("/statistical")}
label="Entity Statistics"
value={entities.length}
color="purple"
/>
<IconCard Icon={BsPersonFillGear}
onClick={() => router.push("/users/performance")}
label="Student Performance"
value={usersCount.student}
color="purple"
/>
<IconCard
Icon={BsEnvelopePaper}
onClick={() => router.push("/assignments")}
label="Assignments"
value={assignmentsCount}
color="purple"
/>
</section>
return (
<>
<Head>
<title>EnCoach</title>
<meta
name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<ToastContainer />
<>
<section className="grid grid-cols-5 place-items-center -md:grid-cols-2 gap-4 text-center">
<IconCard
onClick={() => router.push("/users?type=student")}
Icon={BsPersonFill}
label="Students"
value={usersCount.student}
color="purple"
/>
<IconCard
onClick={() => router.push("/users?type=teacher")}
Icon={BsPencilSquare}
label="Teachers"
value={usersCount.teacher}
color="purple"
/>
<IconCard
Icon={BsBank}
onClick={() => router.push("/users?type=corporate")}
label="Corporates"
value={usersCount.corporate}
color="purple"
/>
<IconCard
Icon={BsBank}
onClick={() => router.push("/users?type=mastercorporate")}
label="Master Corporates"
value={usersCount.mastercorporate}
color="purple"
/>
<IconCard
Icon={BsPeople}
onClick={() => router.push("/classrooms")}
label="Classrooms"
value={groupsCount}
color="purple"
/>
<IconCard
Icon={BsPeopleFill}
onClick={() => router.push("/entities")}
label="Entities"
value={entities.length}
color="purple"
/>
<IconCard
Icon={BsPersonFillGear}
onClick={() => router.push("/statistical")}
label="Entity Statistics"
value={entities.length}
color="purple"
/>
<IconCard
Icon={BsPersonFillGear}
onClick={() => router.push("/users/performance")}
label="Student Performance"
value={usersCount.student}
color="purple"
/>
<IconCard
Icon={BsEnvelopePaper}
onClick={() => router.push("/assignments")}
label="Assignments"
value={assignmentsCount}
color="purple"
/>
</section>
<section className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full justify-between">
<UserDisplayList
users={latestStudents}
title="Latest Students"
/>
<UserDisplayList
users={latestTeachers}
title="Latest Teachers"
/>
<UserDisplayList
users={students}
title="Highest level students"
/>
<UserDisplayList
users={
students
.sort(
(a, b) =>
Object.keys(groupByExam(filterBy(stats, "user", b))).length -
Object.keys(groupByExam(filterBy(stats, "user", a))).length,
)
}
title="Highest exam count students"
/>
</section>
</Layout>
</>
);
<section className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full justify-between">
<UserDisplayList users={latestStudents} title="Latest Students" />
<UserDisplayList users={latestTeachers} title="Latest Teachers" />
<UserDisplayList users={students} title="Highest level students" />
<UserDisplayList
users={students.sort(
(a, b) =>
Object.keys(groupByExam(filterBy(stats, "user", b.id))).length -
Object.keys(groupByExam(filterBy(stats, "user", a.id))).length
)}
title="Highest exam count students"
/>
</section>
</>
</>
);
}