ENCOA-270, ENCOA-265, ENCOA-262, ENCOA-258

This commit is contained in:
Tiago Ribeiro
2024-12-11 16:54:37 +00:00
parent eabfcd026b
commit 71ed1013b7
10 changed files with 68 additions and 48 deletions

View File

@@ -171,7 +171,8 @@ export default function Sidebar({
badge={totalAssignedTickets} badge={totalAssignedTickets}
/> />
)} )}
{(entitiesAllowGeneration.length > 0 || isAdmin) && ( {checkAccess(user, ["admin", "developer", "teacher", 'corporate', 'mastercorporate'])
&& (entitiesAllowGeneration.length > 0 || isAdmin) && (
<Nav <Nav
disabled={disableNavigation} disabled={disableNavigation}
Icon={BsCloudFill} Icon={BsCloudFill}

View File

@@ -155,6 +155,7 @@ export interface GroupWithUsers extends Omit<Group, "participants" | "admin"> {
export interface Code { export interface Code {
id: string; id: string;
code: string; code: string;
entity: string
creator: string; creator: string;
expiryDate: Date; expiryDate: Date;
type: Type; type: Type;

View File

@@ -55,7 +55,7 @@ export default function Lists({ user, entities = [], permissions }: Props) {
Exam List Exam List
</Tab> </Tab>
)} )}
{checkAccess(user, ["developer", "admin", "corporate"]) && entitiesViewCodes.length > 0 && ( {checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"]) && entitiesViewCodes.length > 0 && (
<Tab <Tab
className={({ selected }) => className={({ selected }) =>
clsx( clsx(

View File

@@ -15,9 +15,11 @@ import { ToastContainer } from "react-toastify";
import useDiscounts from "@/hooks/useDiscounts"; import useDiscounts from "@/hooks/useDiscounts";
import PaymobPayment from "@/components/PaymobPayment"; import PaymobPayment from "@/components/PaymobPayment";
import moment from "moment"; import moment from "moment";
import { EntityWithRoles } from "@/interfaces/entity";
interface Props { interface Props {
user: User; user: User;
entities: EntityWithRoles[]
hasExpired?: boolean; hasExpired?: boolean;
reload: () => void; reload: () => void;
} }

View File

@@ -36,6 +36,7 @@ import {
BsPersonFillGear, BsPersonFillGear,
} from "react-icons/bs"; } from "react-icons/bs";
import { ToastContainer } from "react-toastify"; import { ToastContainer } from "react-toastify";
import { useAllowedEntities } from "@/hooks/useEntityPermissions";
interface Props { interface Props {
user: User; user: User;
@@ -67,6 +68,8 @@ export default function Dashboard({ user, users, entities, assignments, stats, g
const students = useMemo(() => users.filter((u) => u.type === "student"), [users]); const students = useMemo(() => users.filter((u) => u.type === "student"), [users]);
const teachers = useMemo(() => users.filter((u) => u.type === "teacher"), [users]); const teachers = useMemo(() => users.filter((u) => u.type === "teacher"), [users]);
const allowedEntityStatistics = useAllowedEntities(user, entities, 'view_entity_statistics')
const router = useRouter(); const router = useRouter();
const averageLevelCalculator = (studentStats: Stat[]) => { const averageLevelCalculator = (studentStats: Stat[]) => {
@@ -94,16 +97,6 @@ export default function Dashboard({ user, users, entities, assignments, stats, g
return calculateAverageLevel(levels); return calculateAverageLevel(levels);
}; };
const UserDisplay = (displayUser: User) => (
<div className="flex w-full p-4 gap-4 items-center hover:bg-mti-purple-ultralight cursor-pointer transition ease-in-out duration-300">
<img src={displayUser.profilePicture} alt={displayUser.name} className="rounded-full w-10 h-10" />
<div className="flex flex-col gap-1 items-start">
<span>{displayUser.name}</span>
<span className="text-sm opacity-75">{displayUser.email}</span>
</div>
</div>
);
return ( return (
<> <>
<Head> <Head>
@@ -152,7 +145,14 @@ export default function Dashboard({ user, users, entities, assignments, stats, g
color="purple" color="purple"
/> />
<IconCard Icon={BsClipboard2Data} label="Exams Performed" value={uniqBy(stats, "exam").length} color="purple" /> <IconCard Icon={BsClipboard2Data} label="Exams Performed" value={uniqBy(stats, "exam").length} color="purple" />
<IconCard Icon={BsPaperclip} label="Average Level" value={averageLevelCalculator(stats).toFixed(1)} color="purple" /> {allowedEntityStatistics.length > 0 && (
<IconCard Icon={BsPersonFillGear}
onClick={() => router.push("/statistical")}
label="Entity Statistics"
value={allowedEntityStatistics.length}
color="purple"
/>
)}
<IconCard Icon={BsPersonFillGear} <IconCard Icon={BsPersonFillGear}
onClick={() => router.push("/users/performance")} onClick={() => router.push("/users/performance")}
label="Student Performance" label="Student Performance"

View File

@@ -21,11 +21,12 @@ import { uniqBy } from "lodash";
import Head from "next/head"; import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useMemo } from "react"; import { useMemo } from "react";
import { BsClipboard2Data, BsEnvelopePaper, BsPaperclip, BsPeople, BsPersonFill } from "react-icons/bs"; import { BsClipboard2Data, BsEnvelopePaper, BsPaperclip, BsPeople, BsPersonFill, BsPersonFillGear } from "react-icons/bs";
import { ToastContainer } from "react-toastify"; import { ToastContainer } from "react-toastify";
import { requestUser } from "@/utils/api"; import { requestUser } from "@/utils/api";
import { useAllowedEntities } from "@/hooks/useEntityPermissions"; import { useAllowedEntities } from "@/hooks/useEntityPermissions";
import { filterAllowedUsers } from "@/utils/users.be"; import { filterAllowedUsers } from "@/utils/users.be";
import { isAdmin } from "@/utils/users";
interface Props { interface Props {
user: User; user: User;
@@ -44,7 +45,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
return redirect("/") return redirect("/")
const entityIDS = mapBy(user.entities, "id") || []; const entityIDS = mapBy(user.entities, "id") || [];
const entities = await getEntitiesWithRoles(entityIDS); const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDS);
const users = await filterAllowedUsers(user, entities) const users = await filterAllowedUsers(user, entities)
const assignments = await getEntitiesAssignments(entityIDS); const assignments = await getEntitiesAssignments(entityIDS);
@@ -58,6 +59,8 @@ export default function Dashboard({ user, users, entities, assignments, stats, g
const students = useMemo(() => users.filter((u) => u.type === "student"), [users]); const students = useMemo(() => users.filter((u) => u.type === "student"), [users]);
const router = useRouter(); const router = useRouter();
const allowedEntityStatistics = useAllowedEntities(user, entities, 'view_entity_statistics')
const averageLevelCalculator = (studentStats: Stat[]) => { const averageLevelCalculator = (studentStats: Stat[]) => {
const formattedStats = studentStats const formattedStats = studentStats
.map((s) => ({ .map((s) => ({
@@ -83,16 +86,6 @@ export default function Dashboard({ user, users, entities, assignments, stats, g
return calculateAverageLevel(levels); return calculateAverageLevel(levels);
}; };
const UserDisplay = (displayUser: User) => (
<div className="flex w-full p-4 gap-4 items-center hover:bg-mti-purple-ultralight cursor-pointer transition ease-in-out duration-300">
<img src={displayUser.profilePicture} alt={displayUser.name} className="rounded-full w-10 h-10" />
<div className="flex flex-col gap-1 items-start">
<span>{displayUser.name}</span>
<span className="text-sm opacity-75">{displayUser.email}</span>
</div>
</div>
);
return ( return (
<> <>
<Head> <Head>
@@ -128,7 +121,14 @@ export default function Dashboard({ user, users, entities, assignments, stats, g
color="purple" color="purple"
/> />
<IconCard Icon={BsClipboard2Data} label="Exams Performed" value={uniqBy(stats, "exam").length} color="purple" /> <IconCard Icon={BsClipboard2Data} label="Exams Performed" value={uniqBy(stats, "exam").length} color="purple" />
<IconCard Icon={BsPaperclip} label="Average Level" value={averageLevelCalculator(stats).toFixed(1)} color="purple" /> {allowedEntityStatistics.length > 0 && (
<IconCard Icon={BsPersonFillGear}
onClick={() => router.push("/statistical")}
label="Entity Statistics"
value={allowedEntityStatistics.length}
color="purple"
/>
)}
<IconCard <IconCard
Icon={BsEnvelopePaper} Icon={BsEnvelopePaper}
onClick={() => router.push("/assignments")} onClick={() => router.push("/assignments")}

View File

@@ -92,7 +92,8 @@ const ENTITY_MANAGEMENT: PermissionLayout[] = [
{ label: "Edit Role Permissions", key: "edit_role_permissions" }, { label: "Edit Role Permissions", key: "edit_role_permissions" },
{ label: "Assign Role to User", key: "assign_to_role" }, { label: "Assign Role to User", key: "assign_to_role" },
{ label: "Delete Entity Role", key: "delete_entity_role" }, { label: "Delete Entity Role", key: "delete_entity_role" },
{ label: "Download Statistics Report", key: "download_statistics_report" } { label: "Download Statistics Report", key: "download_statistics_report" },
{ label: "Edit Grading System", key: "edit_grading_system" }
] ]
const ASSIGNMENT_MANAGEMENT: PermissionLayout[] = [ const ASSIGNMENT_MANAGEMENT: PermissionLayout[] = [

View File

@@ -6,19 +6,30 @@ import useUser from "@/hooks/useUser";
import PaymentDue from "./(status)/PaymentDue"; import PaymentDue from "./(status)/PaymentDue";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { requestUser } from "@/utils/api"; import { requestUser } from "@/utils/api";
import { redirect } from "@/utils"; import { mapBy, redirect, serialize } from "@/utils";
import { getEntities } from "@/utils/entities.be";
import { isAdmin } from "@/utils/users";
import { EntityWithRoles } from "@/interfaces/entity";
import { User } from "@/interfaces/user";
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res) const user = await requestUser(req, res)
if (!user) return redirect("/login") if (!user) return redirect("/login")
const entityIDs = mapBy(user.entities, 'id')
const entities = await getEntities(isAdmin(user) ? undefined : entityIDs)
return { return {
props: {user}, props: serialize({ user, entities }),
}; };
}, sessionOptions); }, sessionOptions);
export default function Home() { interface Props {
const {user} = useUser({redirectTo: "/login"}); user: User,
entities: EntityWithRoles[]
}
export default function Home({ user, entities }: Props) {
const router = useRouter(); const router = useRouter();
return ( return (
@@ -32,7 +43,7 @@ export default function Home() {
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
{user && <PaymentDue user={user} reload={router.reload} />} <PaymentDue entities={entities} user={user} reload={router.reload} />
</> </>
); );
} }

View File

@@ -70,6 +70,7 @@ export default function Admin({ user, entities, permissions, allUsers, entitiesG
const entitiesAllowCreateUsers = useAllowedEntities(user, entities, 'create_user_batch') const entitiesAllowCreateUsers = useAllowedEntities(user, entities, 'create_user_batch')
const entitiesAllowCreateCode = useAllowedEntities(user, entities, 'create_code') const entitiesAllowCreateCode = useAllowedEntities(user, entities, 'create_code')
const entitiesAllowCreateCodes = useAllowedEntities(user, entities, 'create_code_batch') const entitiesAllowCreateCodes = useAllowedEntities(user, entities, 'create_code_batch')
const entitiesAllowEditGrading = useAllowedEntities(user, entities, 'edit_grading_system')
return ( return (
<> <>
@@ -111,7 +112,7 @@ export default function Admin({ user, entities, permissions, allUsers, entitiesG
<CorporateGradingSystem <CorporateGradingSystem
user={user} user={user}
entitiesGrading={entitiesGrading} entitiesGrading={entitiesGrading}
entities={entities} entities={entitiesAllowEditGrading}
mutate={() => router.replace(router.asPath)} mutate={() => router.replace(router.asPath)}
/> />
</Modal> </Modal>
@@ -159,6 +160,7 @@ export default function Admin({ user, entities, permissions, allUsers, entitiesG
color="purple" color="purple"
className="w-full h-full col-span-2" className="w-full h-full col-span-2"
onClick={() => setModalOpen("gradingSystem")} onClick={() => setModalOpen("gradingSystem")}
disabled={entitiesAllowEditGrading.length === 0}
/> />
)} )}
</div> </div>

View File

@@ -57,7 +57,8 @@ export type RolePermission =
"view_code_list" | "view_code_list" |
"delete_code" | "delete_code" |
"view_statistics" | "view_statistics" |
"download_statistics_report" "download_statistics_report" |
"edit_grading_system"
export const DEFAULT_PERMISSIONS: RolePermission[] = [ export const DEFAULT_PERMISSIONS: RolePermission[] = [
"view_students", "view_students",
@@ -128,5 +129,6 @@ export const ADMIN_PERMISSIONS: RolePermission[] = [
"view_code_list", "view_code_list",
"delete_code", "delete_code",
"view_statistics", "view_statistics",
"download_statistics_report" "download_statistics_report",
"edit_grading_system"
] ]