Did the same to all of the dashboards
This commit is contained in:
@@ -51,8 +51,7 @@ export default function AdminDashboard({user}: Props) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(reload, [page]);
|
||||
|
||||
const inactiveCountryManagerFilter = (x: User) =>
|
||||
x.type === "agent" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate));
|
||||
const inactiveCountryManagerFilter = (x: User) => x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate);
|
||||
|
||||
const UserDisplay = (displayUser: User) => (
|
||||
<div
|
||||
@@ -72,17 +71,17 @@ export default function AdminDashboard({user}: Props) {
|
||||
|
||||
const StudentsList = () => {
|
||||
const filter = (x: User) =>
|
||||
x.type === "student" &&
|
||||
(!!selectedUser
|
||||
!!selectedUser
|
||||
? groups
|
||||
.filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id))
|
||||
.flatMap((g) => g.participants)
|
||||
.includes(x.id)
|
||||
: true);
|
||||
: true;
|
||||
|
||||
return (
|
||||
<UserList
|
||||
user={user}
|
||||
type="student"
|
||||
filters={[filter]}
|
||||
renderHeader={(total) => (
|
||||
<div className="flex flex-col gap-4">
|
||||
@@ -101,17 +100,17 @@ export default function AdminDashboard({user}: Props) {
|
||||
|
||||
const TeachersList = () => {
|
||||
const filter = (x: User) =>
|
||||
x.type === "teacher" &&
|
||||
(!!selectedUser
|
||||
!!selectedUser
|
||||
? groups
|
||||
.filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id))
|
||||
.flatMap((g) => g.participants)
|
||||
.includes(x.id) || false
|
||||
: true);
|
||||
: true;
|
||||
|
||||
return (
|
||||
<UserList
|
||||
user={user}
|
||||
type="teacher"
|
||||
filters={[filter]}
|
||||
renderHeader={(total) => (
|
||||
<div className="flex flex-col gap-4">
|
||||
@@ -129,12 +128,10 @@ export default function AdminDashboard({user}: Props) {
|
||||
};
|
||||
|
||||
const AgentsList = () => {
|
||||
const filter = (x: User) => x.type === "agent";
|
||||
|
||||
return (
|
||||
<UserList
|
||||
user={user}
|
||||
filters={[filter]}
|
||||
type="agent"
|
||||
renderHeader={(total) => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div
|
||||
@@ -153,7 +150,7 @@ export default function AdminDashboard({user}: Props) {
|
||||
const CorporateList = () => (
|
||||
<UserList
|
||||
user={user}
|
||||
filters={[(x) => x.type === "corporate"]}
|
||||
type="corporate"
|
||||
renderHeader={(total) => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div
|
||||
@@ -170,11 +167,12 @@ export default function AdminDashboard({user}: Props) {
|
||||
|
||||
const CorporatePaidStatusList = ({paid}: {paid: Boolean}) => {
|
||||
const list = paid ? done : pending;
|
||||
const filter = (x: User) => x.type === "corporate" && list.includes(x.id);
|
||||
const filter = (x: User) => list.includes(x.id);
|
||||
|
||||
return (
|
||||
<UserList
|
||||
user={user}
|
||||
type="corporate"
|
||||
filters={[filter]}
|
||||
renderHeader={(total) => (
|
||||
<div className="flex flex-col gap-4">
|
||||
@@ -197,6 +195,7 @@ export default function AdminDashboard({user}: Props) {
|
||||
return (
|
||||
<UserList
|
||||
user={user}
|
||||
type="agent"
|
||||
filters={[inactiveCountryManagerFilter]}
|
||||
renderHeader={(total) => (
|
||||
<div className="flex flex-col gap-4">
|
||||
@@ -214,11 +213,12 @@ export default function AdminDashboard({user}: Props) {
|
||||
};
|
||||
|
||||
const InactiveStudentsList = () => {
|
||||
const filter = (x: User) => x.type === "student" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate));
|
||||
const filter = (x: User) => x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate);
|
||||
|
||||
return (
|
||||
<UserList
|
||||
user={user}
|
||||
type="student"
|
||||
filters={[filter]}
|
||||
renderHeader={(total) => (
|
||||
<div className="flex flex-col gap-4">
|
||||
@@ -236,12 +236,13 @@ export default function AdminDashboard({user}: Props) {
|
||||
};
|
||||
|
||||
const InactiveCorporateList = () => {
|
||||
const filter = (x: User) => x.type === "corporate" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate));
|
||||
const filter = (x: User) => x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate);
|
||||
|
||||
return (
|
||||
<UserList
|
||||
user={user}
|
||||
filters={[filter]}
|
||||
type="corporate"
|
||||
renderHeader={(total) => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div
|
||||
|
||||
@@ -161,11 +161,13 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const {data: stats} = useFilterRecordsByUser<Stat[]>();
|
||||
const {users, reload, isLoading} = useUsers();
|
||||
const {groups} = useGroups({admin: user.id});
|
||||
const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id});
|
||||
const {balance} = useUserBalance();
|
||||
|
||||
const {users: students, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers({type: "student"});
|
||||
const {users: teachers, reload: reloadTeachers, isLoading: isTeachersLoading} = useUsers({type: "teacher"});
|
||||
|
||||
const appendUserFilters = useFilterStore((state) => state.appendUserFilter);
|
||||
const router = useRouter();
|
||||
|
||||
@@ -173,26 +175,21 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
|
||||
const assignmentsUsers = useMemo(
|
||||
() =>
|
||||
users.filter(
|
||||
(x) =>
|
||||
(x.type === "student" || x.type === "teacher") &&
|
||||
(!!selectedUser
|
||||
[...teachers, ...students].filter((x) =>
|
||||
!!selectedUser
|
||||
? groups
|
||||
.filter((g) => g.admin === selectedUser.id)
|
||||
.flatMap((g) => g.participants)
|
||||
.includes(x.id) || false
|
||||
: groups.flatMap((g) => g.participants).includes(x.id)),
|
||||
: groups.flatMap((g) => g.participants).includes(x.id),
|
||||
),
|
||||
[groups, users, selectedUser],
|
||||
[groups, teachers, students, selectedUser],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setShowModal(!!selectedUser && router.asPath === "/#");
|
||||
}, [selectedUser, router.asPath]);
|
||||
|
||||
const studentFilter = (user: User) => user.type === "student" && groups.flatMap((g) => g.participants).includes(user.id);
|
||||
const teacherFilter = (user: User) => user.type === "teacher" && groups.flatMap((g) => g.participants).includes(user.id);
|
||||
|
||||
const getStatsByStudent = (user: User) => stats.filter((s) => s.user === user.id);
|
||||
|
||||
const UserDisplay = (displayUser: User) => (
|
||||
@@ -228,12 +225,10 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
};
|
||||
|
||||
const StudentPerformancePage = () => {
|
||||
const students = users
|
||||
.filter((x) => x.type === "student" && groups.flatMap((g) => g.participants).includes(x.id))
|
||||
.map((u) => ({
|
||||
const performanceStudents = students.map((u) => ({
|
||||
...u,
|
||||
group: groups.find((x) => x.participants.includes(u.id))?.name || "N/A",
|
||||
corporateName: getUserCompanyName(u, users, groups),
|
||||
corporateName: getUserCompanyName(user, [], groups),
|
||||
}));
|
||||
|
||||
return (
|
||||
@@ -246,13 +241,13 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
<span>Back</span>
|
||||
</div>
|
||||
<div
|
||||
onClick={reload}
|
||||
onClick={reloadStudents}
|
||||
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
|
||||
<span>Reload</span>
|
||||
<BsArrowRepeat className={clsx("text-xl", isLoading && "animate-spin")} />
|
||||
<BsArrowRepeat className={clsx("text-xl", isStudentsLoading && "animate-spin")} />
|
||||
</div>
|
||||
</div>
|
||||
<StudentPerformanceList items={students} stats={stats} users={users} />
|
||||
<StudentPerformanceList items={performanceStudents} stats={stats} users={students} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -260,7 +255,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
const averageLevelCalculator = (studentStats: Stat[]) => {
|
||||
const formattedStats = studentStats
|
||||
.map((s) => ({
|
||||
focus: users.find((u) => u.id === s.user)?.focus,
|
||||
focus: students.find((u) => u.id === s.user)?.focus,
|
||||
score: s.score,
|
||||
module: s.module,
|
||||
}))
|
||||
@@ -292,16 +287,18 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
<section className="grid grid-cols-5 -md:grid-cols-2 gap-4 text-center">
|
||||
<IconCard
|
||||
onClick={() => router.push("/#students")}
|
||||
isLoading={isStudentsLoading}
|
||||
Icon={BsPersonFill}
|
||||
label="Students"
|
||||
value={users.filter(studentFilter).length}
|
||||
value={students.length}
|
||||
color="purple"
|
||||
/>
|
||||
<IconCard
|
||||
onClick={() => router.push("/#teachers")}
|
||||
isLoading={isTeachersLoading}
|
||||
Icon={BsPencilSquare}
|
||||
label="Teachers"
|
||||
value={users.filter(teacherFilter).length}
|
||||
value={teachers.length}
|
||||
color="purple"
|
||||
/>
|
||||
<IconCard
|
||||
@@ -312,6 +309,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
/>
|
||||
<IconCard
|
||||
Icon={BsPaperclip}
|
||||
isLoading={isStudentsLoading}
|
||||
label="Average Level"
|
||||
value={averageLevelCalculator(stats.filter((s) => groups.flatMap((g) => g.participants).includes(s.user))).toFixed(1)}
|
||||
color="purple"
|
||||
@@ -331,8 +329,9 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
/>
|
||||
<IconCard
|
||||
Icon={BsPersonFillGear}
|
||||
isLoading={isStudentsLoading}
|
||||
label="Student Performance"
|
||||
value={users.filter(studentFilter).length}
|
||||
value={students.length}
|
||||
color="purple"
|
||||
onClick={() => router.push("/#studentsPerformance")}
|
||||
/>
|
||||
@@ -354,8 +353,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
<div className="bg-white shadow flex flex-col rounded-xl w-full">
|
||||
<span className="p-4">Latest students</span>
|
||||
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
|
||||
{users
|
||||
.filter(studentFilter)
|
||||
{students
|
||||
.sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))
|
||||
.map((x) => (
|
||||
<UserDisplay key={x.id} {...x} />
|
||||
@@ -365,8 +363,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
<div className="bg-white shadow flex flex-col rounded-xl w-full">
|
||||
<span className="p-4">Latest teachers</span>
|
||||
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
|
||||
{users
|
||||
.filter(teacherFilter)
|
||||
{teachers
|
||||
.sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))
|
||||
.map((x) => (
|
||||
<UserDisplay key={x.id} {...x} />
|
||||
@@ -376,8 +373,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
<div className="bg-white shadow flex flex-col rounded-xl w-full">
|
||||
<span className="p-4">Highest level students</span>
|
||||
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
|
||||
{users
|
||||
.filter(studentFilter)
|
||||
{students
|
||||
.sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels))
|
||||
.map((x) => (
|
||||
<UserDisplay key={x.id} {...x} />
|
||||
@@ -387,8 +383,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
<div className="bg-white shadow flex flex-col rounded-xl w-full">
|
||||
<span className="p-4">Highest exam count students</span>
|
||||
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
|
||||
{users
|
||||
.filter(studentFilter)
|
||||
{students
|
||||
.sort(
|
||||
(a, b) =>
|
||||
Object.keys(groupByExam(getStatsByStudent(b))).length - Object.keys(groupByExam(getStatsByStudent(a))).length,
|
||||
@@ -412,7 +407,8 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
loggedInUser={user}
|
||||
onClose={(shouldReload) => {
|
||||
setSelectedUser(undefined);
|
||||
if (shouldReload) reload();
|
||||
if (shouldReload && selectedUser!.type === "student") reloadStudents();
|
||||
if (shouldReload && selectedUser!.type === "teacher") reloadTeachers();
|
||||
}}
|
||||
onViewStudents={
|
||||
selectedUser.type === "corporate" || selectedUser.type === "teacher"
|
||||
@@ -463,7 +459,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
{router.asPath === "/#students" && (
|
||||
<UserList
|
||||
user={user}
|
||||
filters={[(x) => x.type === "student"]}
|
||||
type="student"
|
||||
renderHeader={(total) => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div
|
||||
@@ -480,7 +476,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
|
||||
{router.asPath === "/#teachers" && (
|
||||
<UserList
|
||||
user={user}
|
||||
filters={[(x) => x.type === "teacher"]}
|
||||
type="teacher"
|
||||
renderHeader={(total) => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, {useMemo} from "react";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import useGroups from "@/hooks/useGroups";
|
||||
import {User} from "@/interfaces/user";
|
||||
@@ -61,29 +61,17 @@ const Card = ({user}: {user: User}) => {
|
||||
};
|
||||
|
||||
const CorporateStudentsLevels = () => {
|
||||
const {users} = useUsers();
|
||||
const {groups} = useGroups({});
|
||||
|
||||
const corporateUsers = users.filter((u) => u.type === "corporate") as User[];
|
||||
const [corporateId, setCorporateId] = React.useState<string>("");
|
||||
const corporate = corporateUsers.find((u) => u.id === corporateId) || corporateUsers[0];
|
||||
|
||||
const groupsFromCorporate = corporate ? groups.filter((g) => g.admin === corporate.id) : [];
|
||||
const {users: students} = useUsers({type: "student"});
|
||||
const {users: corporates} = useUsers({type: "corporate"});
|
||||
|
||||
const groupsParticipants = groupsFromCorporate
|
||||
.flatMap((g) => g.participants)
|
||||
.reduce((accm: User[], p) => {
|
||||
const user = users.find((u) => u.id === p) as User;
|
||||
if (user) {
|
||||
return [...accm, user];
|
||||
}
|
||||
return accm;
|
||||
}, []);
|
||||
const corporate = useMemo(() => corporates.find((u) => u.id === corporateId) || corporates[0], [corporates, corporateId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
options={corporateUsers.map((x: User) => ({
|
||||
options={corporates.map((x: User) => ({
|
||||
value: x.id,
|
||||
label: `${x.name} - ${x.email}`,
|
||||
}))}
|
||||
@@ -98,7 +86,7 @@ const CorporateStudentsLevels = () => {
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
{groupsParticipants.map((u) => (
|
||||
{students.map((u) => (
|
||||
<Card user={u} key={u.id} />
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import clsx from "clsx";
|
||||
import { IconType } from "react-icons";
|
||||
import {IconType} from "react-icons";
|
||||
|
||||
interface Props {
|
||||
Icon: IconType;
|
||||
@@ -9,20 +9,12 @@ interface Props {
|
||||
tooltip?: string;
|
||||
onClick?: () => void;
|
||||
isSelected?: boolean;
|
||||
isLoading?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function IconCard({
|
||||
Icon,
|
||||
label,
|
||||
value,
|
||||
color,
|
||||
tooltip,
|
||||
onClick,
|
||||
className,
|
||||
isSelected,
|
||||
}: Props) {
|
||||
const colorClasses: { [key in typeof color]: string } = {
|
||||
export default function IconCard({Icon, label, value, color, tooltip, onClick, className, isLoading, isSelected}: Props) {
|
||||
const colorClasses: {[key in typeof color]: string} = {
|
||||
purple: "mti-purple-light",
|
||||
red: "mti-red-light",
|
||||
rose: "mti-rose-light",
|
||||
@@ -38,13 +30,12 @@ export default function IconCard({
|
||||
isSelected && `border border-solid border-${colorClasses[color]}`,
|
||||
className,
|
||||
)}
|
||||
data-tip={tooltip}
|
||||
>
|
||||
data-tip={tooltip}>
|
||||
<Icon className={clsx("text-6xl", `text-${colorClasses[color]}`)} />
|
||||
<span className="flex flex-col gap-1 items-center text-xl">
|
||||
<span className="text-lg">{label}</span>
|
||||
<span className={clsx("font-semibold", `text-${colorClasses[color]}`)}>
|
||||
{value}
|
||||
<span className={clsx("font-semibold", `text-${colorClasses[color]}`, isLoading && "animate-pulse")}>
|
||||
{isLoading ? "..." : value}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -296,21 +296,20 @@ const StudentPerformanceList = ({items, stats, users, groups}: {items: StudentPe
|
||||
};
|
||||
|
||||
export default function MasterCorporateDashboard({user}: Props) {
|
||||
const [page, setPage] = useState("");
|
||||
const [selectedUser, setSelectedUser] = useState<User>();
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [corporateAssignments, setCorporateAssignments] = useState<(Assignment & {corporate?: CorporateUser})[]>([]);
|
||||
|
||||
const {data: stats} = useFilterRecordsByUser<Stat[]>();
|
||||
|
||||
const {users: students, reload: reloadStudents} = useUsers({type: "student"});
|
||||
const {users: teachers, reload: reloadTeachers} = useUsers({type: "teacher"});
|
||||
const {users: corporates, reload: reloadCorporates} = useUsers({type: "corporate"});
|
||||
const {users: students, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers({type: "student"});
|
||||
const {users: teachers, reload: reloadTeachers, isLoading: isTeachersLoading} = useUsers({type: "teacher"});
|
||||
const {users: corporates, reload: reloadCorporates, isLoading: isCorporatesLoading} = useUsers({type: "corporate"});
|
||||
|
||||
const {groups} = useGroups({admin: user.id, userType: user.type});
|
||||
const {balance} = useUserBalance();
|
||||
|
||||
const users = useMemo(() => [...students, ...teachers, ...corporates], [corporates, students, teachers]);
|
||||
const users = useMemo(() => uniqBy([...students, ...teachers, ...corporates, user], "id"), [corporates, students, teachers, user]);
|
||||
|
||||
const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id});
|
||||
|
||||
@@ -423,8 +422,22 @@ export default function MasterCorporateDashboard({user}: Props) {
|
||||
const DefaultDashboard = () => (
|
||||
<>
|
||||
<section className="flex flex-wrap gap-2 items-center -lg:justify-center lg:justify-between text-center">
|
||||
<IconCard onClick={() => router.push("/#students")} Icon={BsPersonFill} label="Students" value={students.length} color="purple" />
|
||||
<IconCard onClick={() => router.push("/#teachers")} Icon={BsPencilSquare} label="Teachers" value={teachers.length} color="purple" />
|
||||
<IconCard
|
||||
onClick={() => router.push("/#students")}
|
||||
Icon={BsPersonFill}
|
||||
isLoading={isStudentsLoading}
|
||||
label="Students"
|
||||
value={students.length}
|
||||
color="purple"
|
||||
/>
|
||||
<IconCard
|
||||
onClick={() => router.push("/#teachers")}
|
||||
Icon={BsPencilSquare}
|
||||
isLoading={isTeachersLoading}
|
||||
label="Teachers"
|
||||
value={teachers.length}
|
||||
color="purple"
|
||||
/>
|
||||
<IconCard
|
||||
Icon={BsClipboard2Data}
|
||||
label="Exams Performed"
|
||||
@@ -453,9 +466,17 @@ export default function MasterCorporateDashboard({user}: Props) {
|
||||
value={user.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).format("DD/MM/yyyy") : "Unlimited"}
|
||||
color="rose"
|
||||
/>
|
||||
<IconCard Icon={BsBank} label="Corporate" value={corporates.length} color="purple" onClick={() => router.push("/#corporate")} />
|
||||
<IconCard
|
||||
Icon={BsBank}
|
||||
label="Corporate"
|
||||
value={corporates.length}
|
||||
isLoading={isCorporatesLoading}
|
||||
color="purple"
|
||||
onClick={() => router.push("/#corporate")}
|
||||
/>
|
||||
<IconCard
|
||||
Icon={BsPersonFillGear}
|
||||
isLoading={isStudentsLoading}
|
||||
label="Student Performance"
|
||||
value={students.length}
|
||||
color="purple"
|
||||
@@ -598,7 +619,7 @@ export default function MasterCorporateDashboard({user}: Props) {
|
||||
{router.asPath === "/#students" && (
|
||||
<UserList
|
||||
user={user}
|
||||
filters={[(x) => x.type === "student"]}
|
||||
type="student"
|
||||
renderHeader={(total) => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div
|
||||
@@ -615,7 +636,7 @@ export default function MasterCorporateDashboard({user}: Props) {
|
||||
{router.asPath === "/#teachers" && (
|
||||
<UserList
|
||||
user={user}
|
||||
filters={[(x) => x.type === "teacher"]}
|
||||
type="teacher"
|
||||
renderHeader={(total) => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div
|
||||
@@ -633,7 +654,7 @@ export default function MasterCorporateDashboard({user}: Props) {
|
||||
{router.asPath === "/#corporate" && (
|
||||
<UserList
|
||||
user={user}
|
||||
filters={[(x) => x.type === "corporate"]}
|
||||
type="corporate"
|
||||
renderHeader={(total) => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div
|
||||
|
||||
@@ -24,7 +24,7 @@ import {capitalize} from "lodash";
|
||||
import moment from "moment";
|
||||
import Link from "next/link";
|
||||
import {useRouter} from "next/router";
|
||||
import {useEffect, useState} from "react";
|
||||
import {useEffect, useMemo, useState} from "react";
|
||||
import {BsArrowRepeat, BsBook, BsClipboard, BsFileEarmarkText, BsHeadphones, BsMegaphone, BsPen, BsPencil, BsStar} from "react-icons/bs";
|
||||
import {toast} from "react-toastify";
|
||||
import {activeAssignmentFilter} from "@/utils/assignments";
|
||||
@@ -33,17 +33,20 @@ import useSessions from "@/hooks/useSessions";
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
users: User[];
|
||||
linkedCorporate?: CorporateUser | MasterCorporateUser;
|
||||
}
|
||||
|
||||
export default function StudentDashboard({user, users, linkedCorporate}: Props) {
|
||||
export default function StudentDashboard({user, linkedCorporate}: Props) {
|
||||
const {gradingSystem} = useGradingSystem();
|
||||
const {sessions} = useSessions(user.id);
|
||||
const {data: stats} = useFilterRecordsByUser<Stat[]>(user.id, !user?.id);
|
||||
const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({assignees: user?.id});
|
||||
const {invites, isLoading: isInvitesLoading, reload: reloadInvites} = useInvites({to: user.id});
|
||||
|
||||
const {users: teachers} = useUsers({type: "teacher"});
|
||||
const {users: corporates} = useUsers({type: "corporate"});
|
||||
|
||||
const users = useMemo(() => [...teachers, ...corporates], [teachers, corporates]);
|
||||
const router = useRouter();
|
||||
|
||||
const setExams = useExamStore((state) => state.setExams);
|
||||
|
||||
@@ -63,11 +63,12 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const {data: stats} = useFilterRecordsByUser<Stat[]>();
|
||||
const {users, reload} = useUsers();
|
||||
const {groups} = useGroups({adminAdmins: user.id});
|
||||
const {permissions} = usePermissions(user.id);
|
||||
const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({assigner: user.id});
|
||||
|
||||
const {users: students, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers({type: "student"});
|
||||
|
||||
const appendUserFilters = useFilterStore((state) => state.appendUserFilter);
|
||||
const router = useRouter();
|
||||
|
||||
@@ -75,25 +76,21 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
|
||||
|
||||
const assignmentsUsers = useMemo(
|
||||
() =>
|
||||
users.filter(
|
||||
(x) =>
|
||||
x.type === "student" &&
|
||||
(!!selectedUser
|
||||
students.filter((x) =>
|
||||
!!selectedUser
|
||||
? groups
|
||||
.filter((g) => g.admin === selectedUser.id)
|
||||
.flatMap((g) => g.participants)
|
||||
.includes(x.id)
|
||||
: groups.flatMap((g) => g.participants).includes(x.id)),
|
||||
: groups.flatMap((g) => g.participants).includes(x.id),
|
||||
),
|
||||
[groups, users, selectedUser],
|
||||
[groups, students, selectedUser],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setShowModal(!!selectedUser && router.asPath === "/#");
|
||||
}, [selectedUser, router.asPath]);
|
||||
|
||||
const studentFilter = (user: User) => user.type === "student";
|
||||
|
||||
const getStatsByStudent = (user: User) => stats.filter((s) => s.user === user.id);
|
||||
|
||||
const UserDisplay = (displayUser: User) => (
|
||||
@@ -131,7 +128,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
|
||||
const averageLevelCalculator = (studentStats: Stat[]) => {
|
||||
const formattedStats = studentStats
|
||||
.map((s) => ({
|
||||
focus: users.find((u) => u.id === s.user)?.focus,
|
||||
focus: students.find((u) => u.id === s.user)?.focus,
|
||||
score: s.score,
|
||||
module: s.module,
|
||||
}))
|
||||
@@ -167,9 +164,10 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
|
||||
)}>
|
||||
<IconCard
|
||||
onClick={() => router.push("/#students")}
|
||||
isLoading={isStudentsLoading}
|
||||
Icon={BsPersonFill}
|
||||
label="Students"
|
||||
value={users.filter(studentFilter).length}
|
||||
value={students.length}
|
||||
color="purple"
|
||||
/>
|
||||
<IconCard
|
||||
@@ -181,6 +179,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
|
||||
<IconCard
|
||||
Icon={BsPaperclip}
|
||||
label="Average Level"
|
||||
isLoading={isStudentsLoading}
|
||||
value={averageLevelCalculator(stats.filter((s) => groups.flatMap((g) => g.participants).includes(s.user))).toFixed(1)}
|
||||
color="purple"
|
||||
/>
|
||||
@@ -208,8 +207,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
|
||||
<div className="bg-white shadow flex flex-col rounded-xl w-full">
|
||||
<span className="p-4">Latest students</span>
|
||||
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
|
||||
{users
|
||||
.filter(studentFilter)
|
||||
{students
|
||||
.sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))
|
||||
.map((x) => (
|
||||
<UserDisplay key={x.id} {...x} />
|
||||
@@ -219,8 +217,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
|
||||
<div className="bg-white shadow flex flex-col rounded-xl w-full">
|
||||
<span className="p-4">Highest level students</span>
|
||||
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
|
||||
{users
|
||||
.filter(studentFilter)
|
||||
{students
|
||||
.sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels))
|
||||
.map((x) => (
|
||||
<UserDisplay key={x.id} {...x} />
|
||||
@@ -230,8 +227,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
|
||||
<div className="bg-white shadow flex flex-col rounded-xl w-full">
|
||||
<span className="p-4">Highest exam count students</span>
|
||||
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
|
||||
{users
|
||||
.filter(studentFilter)
|
||||
{students
|
||||
.sort(
|
||||
(a, b) =>
|
||||
Object.keys(groupByExam(getStatsByStudent(b))).length - Object.keys(groupByExam(getStatsByStudent(a))).length,
|
||||
@@ -255,7 +251,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
|
||||
loggedInUser={user}
|
||||
onClose={(shouldReload) => {
|
||||
setSelectedUser(undefined);
|
||||
if (shouldReload) reload();
|
||||
if (shouldReload && selectedUser!.type === "student") reloadStudents();
|
||||
}}
|
||||
onViewStudents={
|
||||
selectedUser.type === "corporate" || selectedUser.type === "teacher"
|
||||
@@ -306,7 +302,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
|
||||
{router.asPath === "/#students" && (
|
||||
<UserList
|
||||
user={user}
|
||||
filters={[(x) => x.type === "student"]}
|
||||
type="student"
|
||||
renderHeader={(total) => (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import {Group, User} from "@/interfaces/user";
|
||||
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);
|
||||
|
||||
interface Props {
|
||||
admin?: string;
|
||||
userType?: string;
|
||||
|
||||
@@ -46,10 +46,12 @@ const CompanyNameCell = ({users, user, groups}: {user: User; users: User[]; grou
|
||||
export default function UserList({
|
||||
user,
|
||||
filters = [],
|
||||
type,
|
||||
renderHeader,
|
||||
}: {
|
||||
user: User;
|
||||
filters?: ((user: User) => boolean)[];
|
||||
type?: Type;
|
||||
renderHeader?: (total: number) => JSX.Element;
|
||||
}) {
|
||||
const [showDemographicInformation, setShowDemographicInformation] = useState(false);
|
||||
@@ -57,7 +59,7 @@ export default function UserList({
|
||||
const [displayUsers, setDisplayUsers] = useState<User[]>([]);
|
||||
const [selectedUser, setSelectedUser] = useState<User>();
|
||||
|
||||
const {users, reload} = useUsers();
|
||||
const {users, reload} = useUsers({type});
|
||||
const {permissions} = usePermissions(user?.id || "");
|
||||
const {balance} = useUserBalance();
|
||||
const {groups} = useGroups({
|
||||
|
||||
@@ -56,10 +56,9 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
|
||||
}
|
||||
|
||||
const linkedCorporate = (await getUserCorporate(user.id)) || null;
|
||||
const users = await getUsers();
|
||||
|
||||
return {
|
||||
props: {user, envVariables, linkedCorporate, users},
|
||||
props: {user, envVariables, linkedCorporate},
|
||||
};
|
||||
}, sessionOptions);
|
||||
|
||||
@@ -67,10 +66,9 @@ interface Props {
|
||||
user: User;
|
||||
envVariables: {[key: string]: string};
|
||||
linkedCorporate?: CorporateUser | MasterCorporateUser;
|
||||
users: User[];
|
||||
}
|
||||
|
||||
export default function Home({users, linkedCorporate}: Props) {
|
||||
export default function Home({linkedCorporate}: Props) {
|
||||
const [showDiagnostics, setShowDiagnostics] = useState(false);
|
||||
const [showDemographicInput, setShowDemographicInput] = useState(false);
|
||||
const [selectedScreen, setSelectedScreen] = useState<Type>("admin");
|
||||
@@ -176,7 +174,7 @@ export default function Home({users, linkedCorporate}: Props) {
|
||||
<ToastContainer />
|
||||
{user && (
|
||||
<Layout user={user}>
|
||||
{checkAccess(user, ["student"]) && <StudentDashboard users={users} linkedCorporate={linkedCorporate} user={user} />}
|
||||
{checkAccess(user, ["student"]) && <StudentDashboard linkedCorporate={linkedCorporate} user={user} />}
|
||||
{checkAccess(user, ["teacher"]) && <TeacherDashboard linkedCorporate={linkedCorporate} user={user} />}
|
||||
{checkAccess(user, ["corporate"]) && <CorporateDashboard linkedCorporate={linkedCorporate} user={user as CorporateUser} />}
|
||||
{checkAccess(user, ["mastercorporate"]) && <MasterCorporateDashboard user={user as MasterCorporateUser} />}
|
||||
@@ -196,7 +194,7 @@ export default function Home({users, linkedCorporate}: Props) {
|
||||
onChange={(value) => (value ? setSelectedScreen(value.value) : setSelectedScreen("admin"))}
|
||||
/>
|
||||
|
||||
{selectedScreen === "student" && <StudentDashboard users={users} linkedCorporate={linkedCorporate} user={user} />}
|
||||
{selectedScreen === "student" && <StudentDashboard linkedCorporate={linkedCorporate} user={user} />}
|
||||
{selectedScreen === "teacher" && <TeacherDashboard linkedCorporate={linkedCorporate} user={user} />}
|
||||
{selectedScreen === "corporate" && (
|
||||
<CorporateDashboard linkedCorporate={linkedCorporate} user={user as unknown as CorporateUser} />
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import Layout from "@/components/High/Layout";
|
||||
import useUser from "@/hooks/useUser";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import { sessionOptions } from "@/lib/session";
|
||||
import {sessionOptions} from "@/lib/session";
|
||||
import useFilterStore from "@/stores/listFilterStore";
|
||||
import { withIronSessionSsr } from "iron-session/next";
|
||||
import {withIronSessionSsr} from "iron-session/next";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
import { BsArrowLeft } from "react-icons/bs";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import {useRouter} from "next/router";
|
||||
import {useEffect} from "react";
|
||||
import {BsArrowLeft} from "react-icons/bs";
|
||||
import {ToastContainer} from "react-toastify";
|
||||
import UserList from "../(admin)/Lists/UserList";
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(({ req, res }) => {
|
||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||
const user = req.session.user;
|
||||
|
||||
const envVariables: { [key: string]: string } = {};
|
||||
const envVariables: {[key: string]: string} = {};
|
||||
Object.keys(process.env)
|
||||
.filter((x) => x.startsWith("NEXT_PUBLIC"))
|
||||
.forEach((x: string) => {
|
||||
@@ -31,17 +31,15 @@ export const getServerSideProps = withIronSessionSsr(({ req, res }) => {
|
||||
}
|
||||
|
||||
return {
|
||||
props: { user: req.session.user, envVariables },
|
||||
props: {user: req.session.user, envVariables},
|
||||
};
|
||||
}, sessionOptions);
|
||||
|
||||
export default function UsersListPage() {
|
||||
const { user } = useUser();
|
||||
const { users } = useUsers();
|
||||
const [filters, clearFilters] = useFilterStore((state) => [
|
||||
state.userFilters,
|
||||
state.clearUserFilters,
|
||||
]);
|
||||
const {user} = useUser();
|
||||
|
||||
const [filters, clearFilters] = useFilterStore((state) => [state.userFilters, state.clearUserFilters]);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
@@ -69,8 +67,7 @@ export default function UsersListPage() {
|
||||
clearFilters();
|
||||
router.back();
|
||||
}}
|
||||
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"
|
||||
>
|
||||
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
|
||||
<BsArrowLeft className="text-xl" />
|
||||
<span>Back</span>
|
||||
</div>
|
||||
|
||||
@@ -53,8 +53,15 @@ export async function getSpecificUsers(ids: string[]) {
|
||||
}
|
||||
|
||||
export async function getLinkedUsers(userID?: string, userType?: Type, type?: Type, page?: number, size?: number) {
|
||||
const q = [
|
||||
...(!!type ? [where("type", "==", type)] : []),
|
||||
orderBy(documentId()),
|
||||
...(page !== undefined && !!size ? [startAt(page * size)] : []),
|
||||
...(page !== undefined && !!size ? [limit(page + 1 * size)] : []),
|
||||
];
|
||||
|
||||
if (!userID || userType === "admin" || userType === "developer") {
|
||||
const snapshot = await getDocs(collection(db, "users"));
|
||||
const snapshot = await getDocs(query(collection(db, "users"), ...q));
|
||||
const users = snapshot.docs.map((doc) => ({
|
||||
id: doc.id,
|
||||
...doc.data(),
|
||||
@@ -73,15 +80,7 @@ export async function getLinkedUsers(userID?: string, userType?: Type, type?: Ty
|
||||
...(userType === "teacher" ? belongingGroups.flatMap((x) => x.participants) : []),
|
||||
]);
|
||||
|
||||
const q = [
|
||||
where(documentId(), "in", participants),
|
||||
...(!!type ? [where("type", "==", type)] : []),
|
||||
orderBy(documentId()),
|
||||
...(page !== undefined && !!size ? [startAt(page * size)] : []),
|
||||
...(page !== undefined && !!size ? [limit(page + 1 * size)] : []),
|
||||
];
|
||||
|
||||
const snapshot = await getDocs(query(collection(db, "users"), ...q));
|
||||
const snapshot = await getDocs(query(collection(db, "users"), ...[where(documentId(), "in", participants), ...q]));
|
||||
const users = snapshot.docs.map((doc) => ({
|
||||
id: doc.id,
|
||||
...doc.data(),
|
||||
|
||||
Reference in New Issue
Block a user