Refactor most getServerProps to fetch independent request in parallel and projected the data only to return the necessary fields and changed some functions

This commit is contained in:
José Marques Lima
2025-01-30 18:25:42 +00:00
parent 58aebaa66c
commit 98ba0bfc04
36 changed files with 5796 additions and 4058 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,16 +5,16 @@ import { useListSearch } from "@/hooks/useListSearch";
import usePagination from "@/hooks/usePagination";
import { EntityWithRoles } from "@/interfaces/entity";
import { Assignment } from "@/interfaces/results";
import { User } from "@/interfaces/user";
import { User } from "@/interfaces/user";
import { sessionOptions } from "@/lib/session";
import { findBy, mapBy, redirect, serialize } from "@/utils";
import { requestUser } from "@/utils/api";
import {
activeAssignmentFilter,
archivedAssignmentFilter,
futureAssignmentFilter,
pastAssignmentFilter,
startHasExpiredAssignmentFilter,
activeAssignmentFilter,
archivedAssignmentFilter,
futureAssignmentFilter,
pastAssignmentFilter,
startHasExpiredAssignmentFilter,
} from "@/utils/assignments";
import { getAssignments, getEntitiesAssignments } from "@/utils/assignments.be";
import { getEntitiesWithRoles } from "@/utils/entities.be";
@@ -28,197 +28,309 @@ import { useMemo } from "react";
import { BsChevronLeft, BsPlus } from "react-icons/bs";
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res)
if (!user) return redirect("/login")
const user = await requestUser(req, res);
if (!user) return redirect("/login");
if (!checkAccess(user, ["admin", "developer", "corporate", "teacher", "mastercorporate"]))
return redirect("/")
if (
!checkAccess(user, [
"admin",
"developer",
"corporate",
"teacher",
"mastercorporate",
])
)
return redirect("/");
const isAdmin = checkAccess(user, ["developer", "admin"]);
const entityIDS = mapBy(user.entities, "id") || [];
const entities = await (isAdmin
? getEntitiesWithRoles()
: getEntitiesWithRoles(entityIDS));
const entityIDS = mapBy(user.entities, "id") || [];
const entities = await (checkAccess(user, ["developer", "admin"]) ? getEntitiesWithRoles() : getEntitiesWithRoles(entityIDS));
const allowedEntities = findAllowedEntities(
user,
entities,
"view_assignments"
);
const [users, assignments] = await Promise.all([
await (isAdmin
? getUsers({}, 0, {}, { _id: 0, id: 1, name: 1 })
: getEntitiesUsers(mapBy(allowedEntities, "id"), {}, 0, {
_id: 0,
id: 1,
name: 1,
})),
await (isAdmin
? getAssignments()
: getEntitiesAssignments(mapBy(allowedEntities, "id"))),
]);
const allowedEntities = findAllowedEntities(user, entities, "view_assignments")
const users =
await (checkAccess(user, ["developer", "admin"]) ? getUsers() : getEntitiesUsers(mapBy(allowedEntities, 'id')));
const assignments =
await (checkAccess(user, ["developer", "admin"]) ? getAssignments() : getEntitiesAssignments(mapBy(allowedEntities, 'id')));
return { props: serialize({ user, users, entities: allowedEntities, assignments }) };
return {
props: serialize({ user, users, entities: allowedEntities, assignments }),
};
}, sessionOptions);
const SEARCH_FIELDS = [["name"]];
interface Props {
assignments: Assignment[];
entities: EntityWithRoles[]
user: User;
users: User[];
assignments: Assignment[];
entities: EntityWithRoles[];
user: User;
users: User[];
}
export default function AssignmentsPage({ assignments, entities, user, users }: Props) {
const entitiesAllowCreate = useAllowedEntities(user, entities, 'create_assignment')
const entitiesAllowEdit = useAllowedEntities(user, entities, 'edit_assignment')
const entitiesAllowArchive = useAllowedEntities(user, entities, 'archive_assignment')
export default function AssignmentsPage({
assignments,
entities,
user,
users,
}: Props) {
const entitiesAllowCreate = useAllowedEntities(
user,
entities,
"create_assignment"
);
const entitiesAllowEdit = useAllowedEntities(
user,
entities,
"edit_assignment"
);
const entitiesAllowArchive = useAllowedEntities(
user,
entities,
"archive_assignment"
);
const activeAssignments = useMemo(() => assignments.filter(activeAssignmentFilter), [assignments]);
const plannedAssignments = useMemo(() => assignments.filter(futureAssignmentFilter), [assignments]);
const pastAssignments = useMemo(() => assignments.filter(pastAssignmentFilter), [assignments]);
const startExpiredAssignments = useMemo(() => assignments.filter(startHasExpiredAssignmentFilter), [assignments]);
const archivedAssignments = useMemo(() => assignments.filter(archivedAssignmentFilter), [assignments]);
const activeAssignments = useMemo(
() => assignments.filter(activeAssignmentFilter),
[assignments]
);
const plannedAssignments = useMemo(
() => assignments.filter(futureAssignmentFilter),
[assignments]
);
const pastAssignments = useMemo(
() => assignments.filter(pastAssignmentFilter),
[assignments]
);
const startExpiredAssignments = useMemo(
() => assignments.filter(startHasExpiredAssignmentFilter),
[assignments]
);
const archivedAssignments = useMemo(
() => assignments.filter(archivedAssignmentFilter),
[assignments]
);
const router = useRouter();
const router = useRouter();
const { rows: activeRows, renderSearch: renderActive } = useListSearch(SEARCH_FIELDS, activeAssignments);
const { rows: plannedRows, renderSearch: renderPlanned } = useListSearch(SEARCH_FIELDS, plannedAssignments);
const { rows: pastRows, renderSearch: renderPast } = useListSearch(SEARCH_FIELDS, pastAssignments);
const { rows: expiredRows, renderSearch: renderExpired } = useListSearch(SEARCH_FIELDS, startExpiredAssignments);
const { rows: archivedRows, renderSearch: renderArchived } = useListSearch(SEARCH_FIELDS, archivedAssignments);
const { rows: activeRows, renderSearch: renderActive } = useListSearch(
SEARCH_FIELDS,
activeAssignments
);
const { rows: plannedRows, renderSearch: renderPlanned } = useListSearch(
SEARCH_FIELDS,
plannedAssignments
);
const { rows: pastRows, renderSearch: renderPast } = useListSearch(
SEARCH_FIELDS,
pastAssignments
);
const { rows: expiredRows, renderSearch: renderExpired } = useListSearch(
SEARCH_FIELDS,
startExpiredAssignments
);
const { rows: archivedRows, renderSearch: renderArchived } = useListSearch(
SEARCH_FIELDS,
archivedAssignments
);
const { items: activeItems, renderMinimal: paginationActive } = usePagination(activeRows, 16);
const { items: plannedItems, renderMinimal: paginationPlanned } = usePagination(plannedRows, 16);
const { items: pastItems, renderMinimal: paginationPast } = usePagination(pastRows, 16);
const { items: expiredItems, renderMinimal: paginationExpired } = usePagination(expiredRows, 16);
const { items: archivedItems, renderMinimal: paginationArchived } = usePagination(archivedRows, 16);
const { items: activeItems, renderMinimal: paginationActive } = usePagination(
activeRows,
16
);
const { items: plannedItems, renderMinimal: paginationPlanned } =
usePagination(plannedRows, 16);
const { items: pastItems, renderMinimal: paginationPast } = usePagination(
pastRows,
16
);
const { items: expiredItems, renderMinimal: paginationExpired } =
usePagination(expiredRows, 16);
const { items: archivedItems, renderMinimal: paginationArchived } =
usePagination(archivedRows, 16);
return (
<>
<Head>
<title>Assignments | 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>
<>
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Link href="/dashboard" className="text-mti-purple hover:text-mti-purple-dark transition ease-in-out duration-300 text-xl">
<BsChevronLeft />
</Link>
<h2 className="font-bold text-2xl">Assignments</h2>
</div>
<Separator />
</div>
<div className="flex flex-col gap-2">
<span className="text-lg font-bold">Active Assignments Status</span>
<div className="flex items-center gap-4">
<span>
<b>Total:</b> {activeAssignments.reduce((acc, curr) => acc + curr.results.length, 0)}/
{activeAssignments.reduce((acc, curr) => curr.exams.length + acc, 0)}
</span>
</div>
</div>
return (
<>
<Head>
<title>Assignments | 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>
<>
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
<Link
href="/dashboard"
className="text-mti-purple hover:text-mti-purple-dark transition ease-in-out duration-300 text-xl"
>
<BsChevronLeft />
</Link>
<h2 className="font-bold text-2xl">Assignments</h2>
</div>
<Separator />
</div>
<div className="flex flex-col gap-2">
<span className="text-lg font-bold">Active Assignments Status</span>
<div className="flex items-center gap-4">
<span>
<b>Total:</b>{" "}
{activeAssignments.reduce(
(acc, curr) => acc + curr.results.length,
0
)}
/
{activeAssignments.reduce(
(acc, curr) => curr.exams.length + acc,
0
)}
</span>
</div>
</div>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Active Assignments ({activeAssignments.length})</h2>
<div className="w-full flex items-center gap-4">
{renderActive()}
{paginationActive()}
</div>
<div className="flex flex-wrap gap-2">
{activeItems.map((a) => (
<AssignmentCard {...a} entityObj={findBy(entities, 'id', a.entity)} users={users} onClick={() => router.push(`/assignments/${a.id}`)} key={a.id} />
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">
Active Assignments ({activeAssignments.length})
</h2>
<div className="w-full flex items-center gap-4">
{renderActive()}
{paginationActive()}
</div>
<div className="flex flex-wrap gap-2">
{activeItems.map((a) => (
<AssignmentCard
{...a}
entityObj={findBy(entities, "id", a.entity)}
users={users}
onClick={() => router.push(`/assignments/${a.id}`)}
key={a.id}
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Planned Assignments ({plannedAssignments.length})</h2>
<div className="w-full flex items-center gap-4">
{renderPlanned()}
{paginationPlanned()}
</div>
<div className="flex flex-wrap gap-2">
<Link
href={entitiesAllowCreate.length > 0 ? "/assignments/creator" : ""}
className="w-[250px] h-[200px] flex flex-col gap-2 items-center justify-center bg-white hover:bg-mti-purple-ultralight text-mti-purple-light hover:text-mti-purple-dark border border-mti-gray-platinum hover:drop-shadow p-4 cursor-pointer rounded-xl transition ease-in-out duration-300">
<BsPlus className="text-6xl" />
<span className="text-lg">New Assignment</span>
</Link>
{plannedItems.map((a) => (
<AssignmentCard
{...a}
users={users}
entityObj={findBy(entities, 'id', a.entity)}
onClick={
mapBy(entitiesAllowEdit, 'id').includes(a.entity || "")
? () => router.push(`/assignments/creator/${a.id}`)
: () => router.push(`/assignments/${a.id}`)
}
key={a.id}
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">
Planned Assignments ({plannedAssignments.length})
</h2>
<div className="w-full flex items-center gap-4">
{renderPlanned()}
{paginationPlanned()}
</div>
<div className="flex flex-wrap gap-2">
<Link
href={
entitiesAllowCreate.length > 0 ? "/assignments/creator" : ""
}
className="w-[250px] h-[200px] flex flex-col gap-2 items-center justify-center bg-white hover:bg-mti-purple-ultralight text-mti-purple-light hover:text-mti-purple-dark border border-mti-gray-platinum hover:drop-shadow p-4 cursor-pointer rounded-xl transition ease-in-out duration-300"
>
<BsPlus className="text-6xl" />
<span className="text-lg">New Assignment</span>
</Link>
{plannedItems.map((a) => (
<AssignmentCard
{...a}
users={users}
entityObj={findBy(entities, "id", a.entity)}
onClick={
mapBy(entitiesAllowEdit, "id").includes(a.entity || "")
? () => router.push(`/assignments/creator/${a.id}`)
: () => router.push(`/assignments/${a.id}`)
}
key={a.id}
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Past Assignments ({pastAssignments.length})</h2>
<div className="w-full flex items-center gap-4">
{renderPast()}
{paginationPast()}
</div>
<div className="flex flex-wrap gap-2">
{pastItems.map((a) => (
<AssignmentCard
{...a}
users={users}
entityObj={findBy(entities, 'id', a.entity)}
onClick={() => router.push(`/assignments/${a.id}`)}
key={a.id}
allowDownload
allowArchive={mapBy(entitiesAllowArchive, 'id').includes(a.entity || "")}
allowExcelDownload
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Assignments start expired ({startExpiredAssignments.length})</h2>
<div className="w-full flex items-center gap-4">
{renderExpired()}
{paginationExpired()}
</div>
<div className="flex flex-wrap gap-2">
{expiredItems.map((a) => (
<AssignmentCard
{...a}
users={users}
entityObj={findBy(entities, 'id', a.entity)}
onClick={() => router.push(`/assignments/${a.id}`)}
key={a.id}
allowDownload
allowArchive={mapBy(entitiesAllowArchive, 'id').includes(a.entity || "")}
allowExcelDownload
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Archived Assignments ({archivedAssignments.length})</h2>
<div className="w-full flex items-center gap-4">
{renderArchived()}
{paginationArchived()}
</div>
<div className="flex flex-wrap gap-2">
{archivedItems.map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => router.push(`/assignments/${a.id}`)}
key={a.id}
entityObj={findBy(entities, 'id', a.entity)}
allowDownload
allowUnarchive
allowExcelDownload
/>
))}
</div>
</section>
</>
</>
);
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">
Past Assignments ({pastAssignments.length})
</h2>
<div className="w-full flex items-center gap-4">
{renderPast()}
{paginationPast()}
</div>
<div className="flex flex-wrap gap-2">
{pastItems.map((a) => (
<AssignmentCard
{...a}
users={users}
entityObj={findBy(entities, "id", a.entity)}
onClick={() => router.push(`/assignments/${a.id}`)}
key={a.id}
allowDownload
allowArchive={mapBy(entitiesAllowArchive, "id").includes(
a.entity || ""
)}
allowExcelDownload
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">
Assignments start expired ({startExpiredAssignments.length})
</h2>
<div className="w-full flex items-center gap-4">
{renderExpired()}
{paginationExpired()}
</div>
<div className="flex flex-wrap gap-2">
{expiredItems.map((a) => (
<AssignmentCard
{...a}
users={users}
entityObj={findBy(entities, "id", a.entity)}
onClick={() => router.push(`/assignments/${a.id}`)}
key={a.id}
allowDownload
allowArchive={mapBy(entitiesAllowArchive, "id").includes(
a.entity || ""
)}
allowExcelDownload
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">
Archived Assignments ({archivedAssignments.length})
</h2>
<div className="w-full flex items-center gap-4">
{renderArchived()}
{paginationArchived()}
</div>
<div className="flex flex-wrap gap-2">
{archivedItems.map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => router.push(`/assignments/${a.id}`)}
key={a.id}
entityObj={findBy(entities, "id", a.entity)}
allowDownload
allowUnarchive
allowExcelDownload
/>
))}
</div>
</section>
</>
</>
);
}