ENCOA-120: Prevented flicker when leaving page

This commit is contained in:
Tiago Ribeiro
2024-09-02 11:21:38 +01:00
parent 0b88d6bcd1
commit abcb1afd48
6 changed files with 278 additions and 428 deletions

View File

@@ -45,8 +45,8 @@ export default function AdminDashboard({user}: Props) {
const router = useRouter(); const router = useRouter();
useEffect(() => { useEffect(() => {
setShowModal(!!selectedUser && page === ""); setShowModal(!!selectedUser && router.asPath === "/#");
}, [selectedUser, page]); }, [selectedUser, router.asPath]);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(reload, [page]); useEffect(reload, [page]);
@@ -87,7 +87,7 @@ export default function AdminDashboard({user}: Props) {
renderHeader={(total) => ( renderHeader={(total) => (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -116,7 +116,7 @@ export default function AdminDashboard({user}: Props) {
renderHeader={(total) => ( renderHeader={(total) => (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -138,7 +138,7 @@ export default function AdminDashboard({user}: Props) {
renderHeader={(total) => ( renderHeader={(total) => (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -157,7 +157,7 @@ export default function AdminDashboard({user}: Props) {
renderHeader={(total) => ( renderHeader={(total) => (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -179,7 +179,7 @@ export default function AdminDashboard({user}: Props) {
renderHeader={(total) => ( renderHeader={(total) => (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -201,7 +201,7 @@ export default function AdminDashboard({user}: Props) {
renderHeader={(total) => ( renderHeader={(total) => (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -223,7 +223,7 @@ export default function AdminDashboard({user}: Props) {
renderHeader={(total) => ( renderHeader={(total) => (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -245,7 +245,7 @@ export default function AdminDashboard({user}: Props) {
renderHeader={(total) => ( renderHeader={(total) => (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -262,7 +262,7 @@ export default function AdminDashboard({user}: Props) {
<> <>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -281,28 +281,28 @@ export default function AdminDashboard({user}: Props) {
Icon={BsPersonFill} Icon={BsPersonFill}
label="Students" label="Students"
value={users.filter((x) => x.type === "student").length} value={users.filter((x) => x.type === "student").length}
onClick={() => setPage("students")} onClick={() => router.push("/#students")}
color="purple" color="purple"
/> />
<IconCard <IconCard
Icon={BsPencilSquare} Icon={BsPencilSquare}
label="Teachers" label="Teachers"
value={users.filter((x) => x.type === "teacher").length} value={users.filter((x) => x.type === "teacher").length}
onClick={() => setPage("teachers")} onClick={() => router.push("/#teachers")}
color="purple" color="purple"
/> />
<IconCard <IconCard
Icon={BsBank} Icon={BsBank}
label="Corporate" label="Corporate"
value={users.filter((x) => x.type === "corporate").length} value={users.filter((x) => x.type === "corporate").length}
onClick={() => setPage("corporate")} onClick={() => router.push("/#corporate")}
color="purple" color="purple"
/> />
<IconCard <IconCard
Icon={BsBriefcaseFill} Icon={BsBriefcaseFill}
label="Country Managers" label="Country Managers"
value={users.filter((x) => x.type === "agent").length} value={users.filter((x) => x.type === "agent").length}
onClick={() => setPage("agents")} onClick={() => router.push("/#agents")}
color="purple" color="purple"
/> />
<IconCard <IconCard
@@ -312,7 +312,7 @@ export default function AdminDashboard({user}: Props) {
color="purple" color="purple"
/> />
<IconCard <IconCard
onClick={() => setPage("inactiveStudents")} onClick={() => router.push("/#inactiveStudents")}
Icon={BsPersonFill} Icon={BsPersonFill}
label="Inactive Students" label="Inactive Students"
value={ value={
@@ -322,14 +322,14 @@ export default function AdminDashboard({user}: Props) {
color="rose" color="rose"
/> />
<IconCard <IconCard
onClick={() => setPage("inactiveCountryManagers")} onClick={() => router.push("/#inactiveCountryManagers")}
Icon={BsBriefcaseFill} Icon={BsBriefcaseFill}
label="Inactive Country Managers" label="Inactive Country Managers"
value={users.filter(inactiveCountryManagerFilter).length} value={users.filter(inactiveCountryManagerFilter).length}
color="rose" color="rose"
/> />
<IconCard <IconCard
onClick={() => setPage("inactiveCorporate")} onClick={() => router.push("/#inactiveCorporate")}
Icon={BsBank} Icon={BsBank}
label="Inactive Corporate" label="Inactive Corporate"
value={ value={
@@ -338,9 +338,15 @@ export default function AdminDashboard({user}: Props) {
} }
color="rose" color="rose"
/> />
<IconCard onClick={() => setPage("paymentdone")} Icon={BsCurrencyDollar} label="Payment Done" value={done.length} color="purple" />
<IconCard <IconCard
onClick={() => setPage("paymentpending")} onClick={() => router.push("/#paymentdone")}
Icon={BsCurrencyDollar}
label="Payment Done"
value={done.length}
color="purple"
/>
<IconCard
onClick={() => router.push("/#paymentpending")}
Icon={BsCurrencyDollar} Icon={BsCurrencyDollar}
label="Pending Payment" label="Pending Payment"
value={pending.length} value={pending.length}
@@ -352,7 +358,12 @@ export default function AdminDashboard({user}: Props) {
label="Content Management System (CMS)" label="Content Management System (CMS)"
color="green" color="green"
/> />
<IconCard onClick={() => setPage("corporatestudentslevels")} Icon={BsPersonFill} label="Corporate Students Levels" color="purple" /> <IconCard
onClick={() => router.push("/#corporatestudentslevels")}
Icon={BsPersonFill}
label="Corporate Students Levels"
color="purple"
/>
</section> </section>
<section className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 w-full justify-between"> <section className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 w-full justify-between">
@@ -598,17 +609,17 @@ export default function AdminDashboard({user}: Props) {
)} )}
</> </>
</Modal> </Modal>
{page === "students" && <StudentsList />} {router.asPath === "/#students" && <StudentsList />}
{page === "teachers" && <TeachersList />} {router.asPath === "/#teachers" && <TeachersList />}
{page === "corporate" && <CorporateList />} {router.asPath === "/#corporate" && <CorporateList />}
{page === "agents" && <AgentsList />} {router.asPath === "/#agents" && <AgentsList />}
{page === "inactiveStudents" && <InactiveStudentsList />} {router.asPath === "/#inactiveStudents" && <InactiveStudentsList />}
{page === "inactiveCorporate" && <InactiveCorporateList />} {router.asPath === "/#inactiveCorporate" && <InactiveCorporateList />}
{page === "inactiveCountryManagers" && <InactiveCountryManagerList />} {router.asPath === "/#inactiveCountryManagers" && <InactiveCountryManagerList />}
{page === "paymentdone" && <CorporatePaidStatusList paid={true} />} {router.asPath === "/#paymentdone" && <CorporatePaidStatusList paid={true} />}
{page === "paymentpending" && <CorporatePaidStatusList paid={false} />} {router.asPath === "/#paymentpending" && <CorporatePaidStatusList paid={false} />}
{page === "corporatestudentslevels" && <CorporateStudentsLevelsHelper />} {router.asPath === "/#corporatestudentslevels" && <CorporateStudentsLevelsHelper />}
{page === "" && <DefaultDashboard />} {router.asPath === "/" && <DefaultDashboard />}
</> </>
); );
} }

View File

@@ -51,6 +51,7 @@ import List from "@/components/List";
import {getUserCompanyName} from "@/resources/user"; import {getUserCompanyName} from "@/resources/user";
import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments"; import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments";
import useUserBalance from "@/hooks/useUserBalance"; import useUserBalance from "@/hooks/useUserBalance";
import AssignmentsPage from "./views/AssignmentsPage";
interface Props { interface Props {
user: CorporateUser; user: CorporateUser;
@@ -156,15 +157,11 @@ const StudentPerformanceList = ({items, stats, users}: {items: StudentPerformanc
}; };
export default function CorporateDashboard({user, linkedCorporate}: Props) { export default function CorporateDashboard({user, linkedCorporate}: Props) {
const [page, setPage] = useState("");
const [selectedUser, setSelectedUser] = useState<User>(); const [selectedUser, setSelectedUser] = useState<User>();
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const [selectedAssignment, setSelectedAssignment] = useState<Assignment>();
const [isCreatingAssignment, setIsCreatingAssignment] = useState(false);
const {data: stats} = useFilterRecordsByUser<Stat[]>(); const {data: stats} = useFilterRecordsByUser<Stat[]>();
const {users, reload, isLoading} = useUsers(); const {users, reload, isLoading} = useUsers();
const {codes} = useCodes(user.id);
const {groups} = useGroups({admin: user.id}); const {groups} = useGroups({admin: user.id});
const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id}); const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id});
const {balance} = useUserBalance(); const {balance} = useUserBalance();
@@ -190,8 +187,8 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
); );
useEffect(() => { useEffect(() => {
setShowModal(!!selectedUser && page === ""); setShowModal(!!selectedUser && router.asPath === "/#");
}, [selectedUser, page]); }, [selectedUser, router.asPath]);
const studentFilter = (user: User) => user.type === "student" && groups.flatMap((g) => g.participants).includes(user.id); 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 teacherFilter = (user: User) => user.type === "teacher" && groups.flatMap((g) => g.participants).includes(user.id);
@@ -227,7 +224,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
renderHeader={(total) => ( renderHeader={(total) => (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -256,7 +253,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
renderHeader={(total) => ( renderHeader={(total) => (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -275,7 +272,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
<> <>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -288,111 +285,6 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
); );
}; };
const AssignmentsPage = () => {
return (
<>
<AssignmentView
isOpen={!!selectedAssignment && !isCreatingAssignment}
onClose={() => {
setSelectedAssignment(undefined);
setIsCreatingAssignment(false);
reloadAssignments();
}}
assignment={selectedAssignment}
/>
<AssignmentCreator
assignment={selectedAssignment}
groups={assignmentsGroups}
users={assignmentsUsers}
isCreating={isCreatingAssignment}
cancelCreation={() => {
setIsCreatingAssignment(false);
setSelectedAssignment(undefined);
reloadAssignments();
}}
/>
<div className="w-full flex justify-between items-center">
<div
onClick={() => setPage("")}
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>
<div
onClick={reloadAssignments}
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", isAssignmentsLoading && "animate-spin")} />
</div>
</div>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Active Assignments ({assignments.filter(activeAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(activeAssignmentFilter).map((a) => (
<AssignmentCard {...a} users={users} onClick={() => setSelectedAssignment(a)} key={a.id} />
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Planned Assignments ({assignments.filter(futureAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
<div
onClick={() => setIsCreatingAssignment(true)}
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>
</div>
{assignments.filter(futureAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => {
setSelectedAssignment(a);
setIsCreatingAssignment(true);
}}
key={a.id}
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Past Assignments ({assignments.filter(pastAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(pastAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
reload={reloadAssignments}
allowArchive
allowExcelDownload
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Archived Assignments ({assignments.filter(archivedAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(archivedAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
reload={reloadAssignments}
allowUnarchive
allowExcelDownload
/>
))}
</div>
</section>
</>
);
};
const StudentPerformancePage = () => { const StudentPerformancePage = () => {
const students = users const students = users
.filter((x) => x.type === "student" && groups.flatMap((g) => g.participants).includes(x.id)) .filter((x) => x.type === "student" && groups.flatMap((g) => g.participants).includes(x.id))
@@ -406,7 +298,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
<> <>
<div className="w-full flex justify-between items-center"> <div className="w-full flex justify-between items-center">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -457,14 +349,14 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
)} )}
<section className="grid grid-cols-5 -md:grid-cols-2 gap-4 text-center"> <section className="grid grid-cols-5 -md:grid-cols-2 gap-4 text-center">
<IconCard <IconCard
onClick={() => setPage("students")} onClick={() => router.push("/#students")}
Icon={BsPersonFill} Icon={BsPersonFill}
label="Students" label="Students"
value={users.filter(studentFilter).length} value={users.filter(studentFilter).length}
color="purple" color="purple"
/> />
<IconCard <IconCard
onClick={() => setPage("teachers")} onClick={() => router.push("/#teachers")}
Icon={BsPencilSquare} Icon={BsPencilSquare}
label="Teachers" label="Teachers"
value={users.filter(teacherFilter).length} value={users.filter(teacherFilter).length}
@@ -482,7 +374,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
value={averageLevelCalculator(stats.filter((s) => groups.flatMap((g) => g.participants).includes(s.user))).toFixed(1)} value={averageLevelCalculator(stats.filter((s) => groups.flatMap((g) => g.participants).includes(s.user))).toFixed(1)}
color="purple" color="purple"
/> />
<IconCard onClick={() => setPage("groups")} Icon={BsPeople} label="Groups" value={groups.length} color="purple" /> <IconCard onClick={() => router.push("/#groups")} Icon={BsPeople} label="Groups" value={groups.length} color="purple" />
<IconCard <IconCard
Icon={BsPersonCheck} Icon={BsPersonCheck}
label="User Balance" label="User Balance"
@@ -500,11 +392,11 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
label="Student Performance" label="Student Performance"
value={users.filter(studentFilter).length} value={users.filter(studentFilter).length}
color="purple" color="purple"
onClick={() => setPage("studentsPerformance")} onClick={() => router.push("/#studentsPerformance")}
/> />
<button <button
disabled={isAssignmentsLoading} disabled={isAssignmentsLoading}
onClick={() => setPage("assignments")} onClick={() => router.push("/#assignments")}
className="bg-white col-span-2 rounded-xl shadow p-4 flex flex-col gap-4 items-center w-96 h-52 justify-center cursor-pointer hover:shadow-xl transition ease-in-out duration-300"> className="bg-white col-span-2 rounded-xl shadow p-4 flex flex-col gap-4 items-center w-96 h-52 justify-center cursor-pointer hover:shadow-xl transition ease-in-out duration-300">
<BsEnvelopePaper className="text-6xl text-mti-purple-light" /> <BsEnvelopePaper className="text-6xl text-mti-purple-light" />
<span className="flex flex-col gap-1 items-center text-xl"> <span className="flex flex-col gap-1 items-center text-xl">
@@ -626,12 +518,21 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
)} )}
</> </>
</Modal> </Modal>
{page === "students" && <StudentsList />} {router.asPath === "/#students" && <StudentsList />}
{page === "teachers" && <TeachersList />} {router.asPath === "/#teachers" && <TeachersList />}
{page === "groups" && <GroupsList />} {router.asPath === "/#groups" && <GroupsList />}
{page === "assignments" && <AssignmentsPage />} {router.asPath === "/#assignments" && (
{page === "studentsPerformance" && <StudentPerformancePage />} <AssignmentsPage
{page === "" && <DefaultDashboard />} assignments={assignments}
groups={assignmentsGroups}
users={assignmentsUsers}
reloadAssignments={reloadAssignments}
isLoading={isAssignmentsLoading}
onBack={() => router.push("/")}
/>
)}
{router.asPath === "/#studentsPerformance" && <StudentPerformancePage />}
{router.asPath === "/" && <DefaultDashboard />}
</> </>
); );
} }

View File

@@ -54,6 +54,7 @@ import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover";
import MasterStatistical from "./MasterStatistical"; import MasterStatistical from "./MasterStatistical";
import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments"; import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments";
import useUserBalance from "@/hooks/useUserBalance"; import useUserBalance from "@/hooks/useUserBalance";
import AssignmentsPage from "./views/AssignmentsPage";
interface Props { interface Props {
user: MasterCorporateUser; user: MasterCorporateUser;
@@ -298,8 +299,6 @@ export default function MasterCorporateDashboard({user}: Props) {
const [page, setPage] = useState(""); const [page, setPage] = useState("");
const [selectedUser, setSelectedUser] = useState<User>(); const [selectedUser, setSelectedUser] = useState<User>();
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const [selectedAssignment, setSelectedAssignment] = useState<Assignment>();
const [isCreatingAssignment, setIsCreatingAssignment] = useState(false);
const [corporateAssignments, setCorporateAssignments] = useState<(Assignment & {corporate?: CorporateUser})[]>([]); const [corporateAssignments, setCorporateAssignments] = useState<(Assignment & {corporate?: CorporateUser})[]>([]);
const {data: stats} = useFilterRecordsByUser<Stat[]>(); const {data: stats} = useFilterRecordsByUser<Stat[]>();
@@ -336,8 +335,8 @@ export default function MasterCorporateDashboard({user}: Props) {
const router = useRouter(); const router = useRouter();
useEffect(() => { useEffect(() => {
setShowModal(!!selectedUser && page === ""); setShowModal(!!selectedUser && router.asPath === "/");
}, [selectedUser, page]); }, [selectedUser, router.asPath]);
useEffect(() => { useEffect(() => {
setCorporateAssignments( setCorporateAssignments(
@@ -377,7 +376,7 @@ export default function MasterCorporateDashboard({user}: Props) {
renderHeader={(total) => ( renderHeader={(total) => (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -400,7 +399,7 @@ export default function MasterCorporateDashboard({user}: Props) {
renderHeader={(total) => ( renderHeader={(total) => (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -423,7 +422,7 @@ export default function MasterCorporateDashboard({user}: Props) {
renderHeader={(total) => ( renderHeader={(total) => (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -440,7 +439,7 @@ export default function MasterCorporateDashboard({user}: Props) {
<> <>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -466,7 +465,7 @@ export default function MasterCorporateDashboard({user}: Props) {
<> <>
<div className="w-full flex justify-between items-center"> <div className="w-full flex justify-between items-center">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -483,129 +482,6 @@ export default function MasterCorporateDashboard({user}: Props) {
); );
}; };
const AssignmentsPage = () => {
return (
<>
<AssignmentView
isOpen={!!selectedAssignment && !isCreatingAssignment}
onClose={() => {
setSelectedAssignment(undefined);
setIsCreatingAssignment(false);
reloadAssignments();
}}
assignment={selectedAssignment}
/>
<AssignmentCreator
assignment={selectedAssignment}
groups={assignmentsGroups}
users={assignmentsUsers}
isCreating={isCreatingAssignment}
cancelCreation={() => {
setIsCreatingAssignment(false);
setSelectedAssignment(undefined);
reloadAssignments();
}}
/>
<div className="w-full flex justify-between items-center">
<div
onClick={() => setPage("")}
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>
<div
onClick={reloadAssignments}
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", isAssignmentsLoading && "animate-spin")} />
</div>
</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> {assignments.filter(activeAssignmentFilter).reduce((acc, curr) => acc + curr.results.length, 0)}/
{assignments.filter(activeAssignmentFilter).reduce((acc, curr) => curr.exams.length + acc, 0)}
</span>
{Object.keys(groupBy(corporateAssignments, (x) => x.corporate?.id)).map((x) => (
<div key={x}>
<span className="font-semibold">{getUserCompanyName(users.find((u) => u.id === x)!, users, groups)}: </span>
<span>
{groupBy(corporateAssignments, (x) => x.corporate?.id)[x].reduce((acc, curr) => curr.results.length + acc, 0)}/
{groupBy(corporateAssignments, (x) => x.corporate?.id)[x].reduce((acc, curr) => curr.exams.length + acc, 0)}
</span>
</div>
))}
</div>
</div>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Active Assignments ({assignments.filter(activeAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(activeAssignmentFilter).map((a) => (
<AssignmentCard {...a} users={users} onClick={() => setSelectedAssignment(a)} key={a.id} />
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Planned Assignments ({assignments.filter(futureAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
<div
onClick={() => setIsCreatingAssignment(true)}
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>
</div>
{assignments.filter(futureAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => {
setSelectedAssignment(a);
setIsCreatingAssignment(true);
}}
key={a.id}
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Past Assignments ({assignments.filter(pastAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(pastAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
reload={reloadAssignments}
allowArchive
allowExcelDownload
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Archived Assignments ({assignments.filter(archivedAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(archivedAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
reload={reloadAssignments}
allowUnarchive
allowExcelDownload
/>
))}
</div>
</section>
</>
);
};
const masterCorporateUsers = useMemo( const masterCorporateUsers = useMemo(
() => () =>
masterCorporateUserGroups.reduce((accm: CorporateUser[], id) => { masterCorporateUserGroups.reduce((accm: CorporateUser[], id) => {
@@ -621,7 +497,7 @@ export default function MasterCorporateDashboard({user}: Props) {
<> <>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -637,14 +513,14 @@ export default function MasterCorporateDashboard({user}: Props) {
<> <>
<section className="flex flex-wrap gap-2 items-center -lg:justify-center lg:justify-between text-center"> <section className="flex flex-wrap gap-2 items-center -lg:justify-center lg:justify-between text-center">
<IconCard <IconCard
onClick={() => setPage("students")} onClick={() => router.push("/#students")}
Icon={BsPersonFill} Icon={BsPersonFill}
label="Students" label="Students"
value={users.filter(studentFilter).length} value={users.filter(studentFilter).length}
color="purple" color="purple"
/> />
<IconCard <IconCard
onClick={() => setPage("teachers")} onClick={() => router.push("/#teachers")}
Icon={BsPencilSquare} Icon={BsPencilSquare}
label="Teachers" label="Teachers"
value={users.filter(teacherFilter).length} value={users.filter(teacherFilter).length}
@@ -665,7 +541,7 @@ export default function MasterCorporateDashboard({user}: Props) {
).toFixed(1)} ).toFixed(1)}
color="purple" color="purple"
/> />
<IconCard onClick={() => setPage("groups")} Icon={BsPeople} label="Groups" value={groups.length} color="purple" /> <IconCard onClick={() => router.push("/#groups")} Icon={BsPeople} label="Groups" value={groups.length} color="purple" />
<IconCard <IconCard
Icon={BsPersonCheck} Icon={BsPersonCheck}
label="User Balance" label="User Balance"
@@ -683,25 +559,25 @@ export default function MasterCorporateDashboard({user}: Props) {
label="Corporate" label="Corporate"
value={masterCorporateUserGroups.length} value={masterCorporateUserGroups.length}
color="purple" color="purple"
onClick={() => setPage("corporate")} onClick={() => router.push("/#corporate")}
/> />
<IconCard <IconCard
Icon={BsPersonFillGear} Icon={BsPersonFillGear}
label="Student Performance" label="Student Performance"
value={users.filter(studentFilter).length} value={users.filter(studentFilter).length}
color="purple" color="purple"
onClick={() => setPage("studentsPerformance")} onClick={() => router.push("/#studentsPerformance")}
/> />
<IconCard <IconCard
Icon={BsDatabase} Icon={BsDatabase}
label="Master Statistical" label="Master Statistical"
// value={masterCorporateUserGroups.length} // value={masterCorporateUserGroups.length}
color="purple" color="purple"
onClick={() => setPage("statistical")} onClick={() => router.push("/#statistical")}
/> />
<button <button
disabled={isAssignmentsLoading} disabled={isAssignmentsLoading}
onClick={() => setPage("assignments")} onClick={() => router.push("/#assignments")}
className="bg-white col-span-2 rounded-xl shadow p-4 flex flex-col gap-4 items-center w-96 h-52 justify-center cursor-pointer hover:shadow-xl transition ease-in-out duration-300"> className="bg-white col-span-2 rounded-xl shadow p-4 flex flex-col gap-4 items-center w-96 h-52 justify-center cursor-pointer hover:shadow-xl transition ease-in-out duration-300">
<BsEnvelopePaper className="text-6xl text-mti-purple-light" /> <BsEnvelopePaper className="text-6xl text-mti-purple-light" />
<span className="flex flex-col gap-1 items-center text-xl"> <span className="flex flex-col gap-1 items-center text-xl">
@@ -828,14 +704,24 @@ export default function MasterCorporateDashboard({user}: Props) {
)} )}
</> </>
</Modal> </Modal>
{page === "students" && <StudentsList />} {router.asPath === "/#students" && <StudentsList />}
{page === "teachers" && <TeachersList />} {router.asPath === "/#teachers" && <TeachersList />}
{page === "groups" && <GroupsList />} {router.asPath === "/#groups" && <GroupsList />}
{page === "corporate" && <CorporateList />} {router.asPath === "/#corporate" && <CorporateList />}
{page === "assignments" && <AssignmentsPage />} {router.asPath === "/#assignments" && (
{page === "studentsPerformance" && <StudentPerformancePage />} <AssignmentsPage
{page === "statistical" && <MasterStatisticalPage />} assignments={assignments}
{page === "" && <DefaultDashboard />} corporateAssignments={corporateAssignments}
groups={assignmentsGroups}
users={assignmentsUsers}
reloadAssignments={reloadAssignments}
isLoading={isAssignmentsLoading}
onBack={() => router.push("/")}
/>
)}
{router.asPath === "/#studentsPerformance" && <StudentPerformancePage />}
{router.asPath === "/#statistical" && <MasterStatisticalPage />}
{router.asPath === "/" && <DefaultDashboard />}
</> </>
); );
} }

View File

@@ -49,6 +49,8 @@ import {getUserCorporate} from "@/utils/groups";
import {checkAccess} from "@/utils/permissions"; import {checkAccess} from "@/utils/permissions";
import usePermissions from "@/hooks/usePermissions"; import usePermissions from "@/hooks/usePermissions";
import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments"; import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments";
import AssignmentsPage from "./views/AssignmentsPage";
import {useRouter} from "next/router";
interface Props { interface Props {
user: User; user: User;
@@ -56,11 +58,8 @@ interface Props {
} }
export default function TeacherDashboard({user, linkedCorporate}: Props) { export default function TeacherDashboard({user, linkedCorporate}: Props) {
const [page, setPage] = useState("");
const [selectedUser, setSelectedUser] = useState<User>(); const [selectedUser, setSelectedUser] = useState<User>();
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const [selectedAssignment, setSelectedAssignment] = useState<Assignment>();
const [isCreatingAssignment, setIsCreatingAssignment] = useState(false);
const {data: stats} = useFilterRecordsByUser<Stat[]>(); const {data: stats} = useFilterRecordsByUser<Stat[]>();
const {users, reload} = useUsers(); const {users, reload} = useUsers();
@@ -68,6 +67,8 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
const {permissions} = usePermissions(user.id); const {permissions} = usePermissions(user.id);
const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({assigner: user.id}); const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({assigner: user.id});
const router = useRouter();
const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]); const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]);
const assignmentsUsers = useMemo( const assignmentsUsers = useMemo(
@@ -86,8 +87,8 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
); );
useEffect(() => { useEffect(() => {
setShowModal(!!selectedUser && page === ""); setShowModal(!!selectedUser && router.asPath === "/#");
}, [selectedUser, page]); }, [selectedUser, router.asPath]);
const studentFilter = (user: User) => user.type === "student" && groups.flatMap((g) => g.participants).includes(user.id); const studentFilter = (user: User) => user.type === "student" && groups.flatMap((g) => g.participants).includes(user.id);
@@ -122,7 +123,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
renderHeader={(total) => ( renderHeader={(total) => (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -141,7 +142,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
<> <>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div <div
onClick={() => setPage("")} onClick={() => router.push("/")}
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" /> <BsArrowLeft className="text-xl" />
<span>Back</span> <span>Back</span>
@@ -179,111 +180,6 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
return calculateAverageLevel(levels); return calculateAverageLevel(levels);
}; };
const AssignmentsPage = () => {
return (
<>
<AssignmentView
isOpen={!!selectedAssignment && !isCreatingAssignment}
onClose={() => {
setSelectedAssignment(undefined);
setIsCreatingAssignment(false);
reloadAssignments();
}}
assignment={selectedAssignment}
/>
<AssignmentCreator
assignment={selectedAssignment}
groups={assignmentsGroups}
users={assignmentsUsers}
isCreating={isCreatingAssignment}
cancelCreation={() => {
setIsCreatingAssignment(false);
setSelectedAssignment(undefined);
reloadAssignments();
}}
/>
<div className="w-full flex justify-between items-center">
<div
onClick={() => setPage("")}
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>
<div
onClick={reloadAssignments}
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", isAssignmentsLoading && "animate-spin")} />
</div>
</div>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Active Assignments ({assignments.filter(activeAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(activeAssignmentFilter).map((a) => (
<AssignmentCard {...a} users={users} onClick={() => setSelectedAssignment(a)} key={a.id} />
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Planned Assignments ({assignments.filter(futureAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
<div
onClick={() => setIsCreatingAssignment(true)}
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>
</div>
{assignments.filter(futureAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => {
setSelectedAssignment(a);
setIsCreatingAssignment(true);
}}
key={a.id}
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Past Assignments ({assignments.filter(pastAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(pastAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
reload={reloadAssignments}
allowArchive
allowExcelDownload
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Archived Assignments ({assignments.filter(archivedAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(archivedAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
reload={reloadAssignments}
allowUnarchive
allowExcelDownload
/>
))}
</div>
</section>
</>
);
};
const DefaultDashboard = () => ( const DefaultDashboard = () => (
<> <>
{linkedCorporate && ( {linkedCorporate && (
@@ -297,7 +193,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
!!linkedCorporate && "mt-12 xl:mt-6", !!linkedCorporate && "mt-12 xl:mt-6",
)}> )}>
<IconCard <IconCard
onClick={() => setPage("students")} onClick={() => router.push("/#students")}
Icon={BsPersonFill} Icon={BsPersonFill}
label="Students" label="Students"
value={users.filter(studentFilter).length} value={users.filter(studentFilter).length}
@@ -321,11 +217,11 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
label="Groups" label="Groups"
value={groups.filter((x) => x.admin === user.id).length} value={groups.filter((x) => x.admin === user.id).length}
color="purple" color="purple"
onClick={() => setPage("groups")} onClick={() => router.push("/#groups")}
/> />
)} )}
<div <div
onClick={() => setPage("assignments")} onClick={() => router.push("/#assignments")}
className="bg-white rounded-xl shadow p-4 flex flex-col gap-4 items-center w-96 h-52 justify-center cursor-pointer hover:shadow-xl transition ease-in-out duration-300"> className="bg-white rounded-xl shadow p-4 flex flex-col gap-4 items-center w-96 h-52 justify-center cursor-pointer hover:shadow-xl transition ease-in-out duration-300">
<BsEnvelopePaper className="text-6xl text-mti-purple-light" /> <BsEnvelopePaper className="text-6xl text-mti-purple-light" />
<span className="flex flex-col gap-1 items-center text-xl"> <span className="flex flex-col gap-1 items-center text-xl">
@@ -389,19 +285,28 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
if (shouldReload) reload(); if (shouldReload) reload();
}} }}
onViewStudents={ onViewStudents={
selectedUser.type === "corporate" || selectedUser.type === "teacher" ? () => setPage("students") : undefined selectedUser.type === "corporate" || selectedUser.type === "teacher" ? () => router.push("/#students") : undefined
} }
onViewTeachers={selectedUser.type === "corporate" ? () => setPage("teachers") : undefined} onViewTeachers={selectedUser.type === "corporate" ? () => router.push("/#teachers") : undefined}
user={selectedUser} user={selectedUser}
/> />
</div> </div>
)} )}
</> </>
</Modal> </Modal>
{page === "students" && <StudentsList />} {router.asPath === "/#students" && <StudentsList />}
{page === "groups" && <GroupsList />} {router.asPath === "/#groups" && <GroupsList />}
{page === "assignments" && <AssignmentsPage />} {router.asPath === "/#assignments" && (
{page === "" && <DefaultDashboard />} <AssignmentsPage
assignments={assignments}
groups={assignmentsGroups}
users={assignmentsUsers}
reloadAssignments={reloadAssignments}
isLoading={isAssignmentsLoading}
onBack={() => router.push("/")}
/>
)}
{router.asPath === "/" && <DefaultDashboard />}
</> </>
); );
} }

View File

@@ -0,0 +1,147 @@
import {Assignment} from "@/interfaces/results";
import {CorporateUser, Group, User} from "@/interfaces/user";
import {getUserCompanyName} from "@/resources/user";
import {activeAssignmentFilter, archivedAssignmentFilter, futureAssignmentFilter, pastAssignmentFilter} from "@/utils/assignments";
import clsx from "clsx";
import {groupBy} from "lodash";
import {useState} from "react";
import {BsArrowLeft, BsArrowRepeat, BsPlus} from "react-icons/bs";
import AssignmentCard from "../AssignmentCard";
import AssignmentCreator from "../AssignmentCreator";
import AssignmentView from "../AssignmentView";
interface Props {
assignments: Assignment[];
corporateAssignments?: ({corporate?: CorporateUser} & Assignment)[];
groups: Group[];
users: User[];
isLoading: boolean;
onBack: () => void;
reloadAssignments: () => void;
}
export default function AssignmentsPage({assignments, corporateAssignments, groups, users, isLoading, onBack, reloadAssignments}: Props) {
const [selectedAssignment, setSelectedAssignment] = useState<Assignment>();
const [isCreatingAssignment, setIsCreatingAssignment] = useState(false);
return (
<>
<AssignmentView
isOpen={!!selectedAssignment && !isCreatingAssignment}
onClose={() => {
setSelectedAssignment(undefined);
setIsCreatingAssignment(false);
reloadAssignments();
}}
assignment={selectedAssignment}
/>
<AssignmentCreator
assignment={selectedAssignment}
groups={groups}
users={users}
isCreating={isCreatingAssignment}
cancelCreation={() => {
setIsCreatingAssignment(false);
setSelectedAssignment(undefined);
reloadAssignments();
}}
/>
<div className="w-full flex justify-between items-center">
<div
onClick={onBack}
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>
<div
onClick={reloadAssignments}
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")} />
</div>
</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> {assignments.filter(activeAssignmentFilter).reduce((acc, curr) => acc + curr.results.length, 0)}/
{assignments.filter(activeAssignmentFilter).reduce((acc, curr) => curr.exams.length + acc, 0)}
</span>
{Object.keys(groupBy(corporateAssignments, (x) => x.corporate?.id)).map((x) => (
<div key={x}>
<span className="font-semibold">{getUserCompanyName(users.find((u) => u.id === x)!, users, groups)}: </span>
<span>
{groupBy(corporateAssignments, (x) => x.corporate?.id)[x].reduce((acc, curr) => curr.results.length + acc, 0)}/
{groupBy(corporateAssignments, (x) => x.corporate?.id)[x].reduce((acc, curr) => curr.exams.length + acc, 0)}
</span>
</div>
))}
</div>
</div>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Active Assignments ({assignments.filter(activeAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(activeAssignmentFilter).map((a) => (
<AssignmentCard {...a} users={users} onClick={() => setSelectedAssignment(a)} key={a.id} />
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Planned Assignments ({assignments.filter(futureAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
<div
onClick={() => setIsCreatingAssignment(true)}
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>
</div>
{assignments.filter(futureAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => {
setSelectedAssignment(a);
setIsCreatingAssignment(true);
}}
key={a.id}
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Past Assignments ({assignments.filter(pastAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(pastAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
reload={reloadAssignments}
allowArchive
allowExcelDownload
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Archived Assignments ({assignments.filter(archivedAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(archivedAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
reload={reloadAssignments}
allowUnarchive
allowExcelDownload
/>
))}
</div>
</section>
</>
);
}

View File

@@ -22,7 +22,7 @@ export function getUserCompanyName(user: User, users: User[], groups: Group[]) {
if (isCorporateUser(user)) return user.corporateInformation?.companyInformation?.name || user.name; if (isCorporateUser(user)) return user.corporateInformation?.companyInformation?.name || user.name;
if (isAgentUser(user)) return user.agentInformation?.companyName || user.name; if (isAgentUser(user)) return user.agentInformation?.companyName || user.name;
const belongingGroups = groups.filter((x) => x.participants.includes(user.id)); const belongingGroups = groups.filter((x) => x.participants.includes(user?.id));
const belongingGroupsAdmins = belongingGroups.map((x) => users.find((u) => u.id === x.admin)).filter((x) => !!x && isCorporateUser(x)); const belongingGroupsAdmins = belongingGroups.map((x) => users.find((u) => u.id === x.admin)).filter((x) => !!x && isCorporateUser(x));
if (belongingGroupsAdmins.length === 0) return ""; if (belongingGroupsAdmins.length === 0) return "";