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

View File

@@ -51,6 +51,7 @@ import List from "@/components/List";
import {getUserCompanyName} from "@/resources/user";
import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments";
import useUserBalance from "@/hooks/useUserBalance";
import AssignmentsPage from "./views/AssignmentsPage";
interface Props {
user: CorporateUser;
@@ -156,15 +157,11 @@ const StudentPerformanceList = ({items, stats, users}: {items: StudentPerformanc
};
export default function CorporateDashboard({user, linkedCorporate}: Props) {
const [page, setPage] = useState("");
const [selectedUser, setSelectedUser] = useState<User>();
const [showModal, setShowModal] = useState(false);
const [selectedAssignment, setSelectedAssignment] = useState<Assignment>();
const [isCreatingAssignment, setIsCreatingAssignment] = useState(false);
const {data: stats} = useFilterRecordsByUser<Stat[]>();
const {users, reload, isLoading} = useUsers();
const {codes} = useCodes(user.id);
const {groups} = useGroups({admin: user.id});
const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id});
const {balance} = useUserBalance();
@@ -190,8 +187,8 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
);
useEffect(() => {
setShowModal(!!selectedUser && page === "");
}, [selectedUser, page]);
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);
@@ -227,7 +224,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<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">
<BsArrowLeft className="text-xl" />
<span>Back</span>
@@ -256,7 +253,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<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">
<BsArrowLeft className="text-xl" />
<span>Back</span>
@@ -275,7 +272,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
<>
<div className="flex flex-col gap-4">
<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">
<BsArrowLeft className="text-xl" />
<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 students = users
.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
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">
<BsArrowLeft className="text-xl" />
<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">
<IconCard
onClick={() => setPage("students")}
onClick={() => router.push("/#students")}
Icon={BsPersonFill}
label="Students"
value={users.filter(studentFilter).length}
color="purple"
/>
<IconCard
onClick={() => setPage("teachers")}
onClick={() => router.push("/#teachers")}
Icon={BsPencilSquare}
label="Teachers"
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)}
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
Icon={BsPersonCheck}
label="User Balance"
@@ -500,11 +392,11 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
label="Student Performance"
value={users.filter(studentFilter).length}
color="purple"
onClick={() => setPage("studentsPerformance")}
onClick={() => router.push("/#studentsPerformance")}
/>
<button
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">
<BsEnvelopePaper className="text-6xl text-mti-purple-light" />
<span className="flex flex-col gap-1 items-center text-xl">
@@ -626,12 +518,21 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
)}
</>
</Modal>
{page === "students" && <StudentsList />}
{page === "teachers" && <TeachersList />}
{page === "groups" && <GroupsList />}
{page === "assignments" && <AssignmentsPage />}
{page === "studentsPerformance" && <StudentPerformancePage />}
{page === "" && <DefaultDashboard />}
{router.asPath === "/#students" && <StudentsList />}
{router.asPath === "/#teachers" && <TeachersList />}
{router.asPath === "/#groups" && <GroupsList />}
{router.asPath === "/#assignments" && (
<AssignmentsPage
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 {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments";
import useUserBalance from "@/hooks/useUserBalance";
import AssignmentsPage from "./views/AssignmentsPage";
interface Props {
user: MasterCorporateUser;
@@ -298,8 +299,6 @@ export default function MasterCorporateDashboard({user}: Props) {
const [page, setPage] = useState("");
const [selectedUser, setSelectedUser] = useState<User>();
const [showModal, setShowModal] = useState(false);
const [selectedAssignment, setSelectedAssignment] = useState<Assignment>();
const [isCreatingAssignment, setIsCreatingAssignment] = useState(false);
const [corporateAssignments, setCorporateAssignments] = useState<(Assignment & {corporate?: CorporateUser})[]>([]);
const {data: stats} = useFilterRecordsByUser<Stat[]>();
@@ -336,8 +335,8 @@ export default function MasterCorporateDashboard({user}: Props) {
const router = useRouter();
useEffect(() => {
setShowModal(!!selectedUser && page === "");
}, [selectedUser, page]);
setShowModal(!!selectedUser && router.asPath === "/");
}, [selectedUser, router.asPath]);
useEffect(() => {
setCorporateAssignments(
@@ -377,7 +376,7 @@ export default function MasterCorporateDashboard({user}: Props) {
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<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">
<BsArrowLeft className="text-xl" />
<span>Back</span>
@@ -400,7 +399,7 @@ export default function MasterCorporateDashboard({user}: Props) {
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<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">
<BsArrowLeft className="text-xl" />
<span>Back</span>
@@ -423,7 +422,7 @@ export default function MasterCorporateDashboard({user}: Props) {
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<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">
<BsArrowLeft className="text-xl" />
<span>Back</span>
@@ -440,7 +439,7 @@ export default function MasterCorporateDashboard({user}: Props) {
<>
<div className="flex flex-col gap-4">
<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">
<BsArrowLeft className="text-xl" />
<span>Back</span>
@@ -466,7 +465,7 @@ export default function MasterCorporateDashboard({user}: Props) {
<>
<div className="w-full flex justify-between items-center">
<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">
<BsArrowLeft className="text-xl" />
<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(
() =>
masterCorporateUserGroups.reduce((accm: CorporateUser[], id) => {
@@ -621,7 +497,7 @@ export default function MasterCorporateDashboard({user}: Props) {
<>
<div className="flex flex-col gap-4">
<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">
<BsArrowLeft className="text-xl" />
<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">
<IconCard
onClick={() => setPage("students")}
onClick={() => router.push("/#students")}
Icon={BsPersonFill}
label="Students"
value={users.filter(studentFilter).length}
color="purple"
/>
<IconCard
onClick={() => setPage("teachers")}
onClick={() => router.push("/#teachers")}
Icon={BsPencilSquare}
label="Teachers"
value={users.filter(teacherFilter).length}
@@ -665,7 +541,7 @@ export default function MasterCorporateDashboard({user}: Props) {
).toFixed(1)}
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
Icon={BsPersonCheck}
label="User Balance"
@@ -683,25 +559,25 @@ export default function MasterCorporateDashboard({user}: Props) {
label="Corporate"
value={masterCorporateUserGroups.length}
color="purple"
onClick={() => setPage("corporate")}
onClick={() => router.push("/#corporate")}
/>
<IconCard
Icon={BsPersonFillGear}
label="Student Performance"
value={users.filter(studentFilter).length}
color="purple"
onClick={() => setPage("studentsPerformance")}
onClick={() => router.push("/#studentsPerformance")}
/>
<IconCard
Icon={BsDatabase}
label="Master Statistical"
// value={masterCorporateUserGroups.length}
color="purple"
onClick={() => setPage("statistical")}
onClick={() => router.push("/#statistical")}
/>
<button
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">
<BsEnvelopePaper className="text-6xl text-mti-purple-light" />
<span className="flex flex-col gap-1 items-center text-xl">
@@ -828,14 +704,24 @@ export default function MasterCorporateDashboard({user}: Props) {
)}
</>
</Modal>
{page === "students" && <StudentsList />}
{page === "teachers" && <TeachersList />}
{page === "groups" && <GroupsList />}
{page === "corporate" && <CorporateList />}
{page === "assignments" && <AssignmentsPage />}
{page === "studentsPerformance" && <StudentPerformancePage />}
{page === "statistical" && <MasterStatisticalPage />}
{page === "" && <DefaultDashboard />}
{router.asPath === "/#students" && <StudentsList />}
{router.asPath === "/#teachers" && <TeachersList />}
{router.asPath === "/#groups" && <GroupsList />}
{router.asPath === "/#corporate" && <CorporateList />}
{router.asPath === "/#assignments" && (
<AssignmentsPage
assignments={assignments}
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 usePermissions from "@/hooks/usePermissions";
import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments";
import AssignmentsPage from "./views/AssignmentsPage";
import {useRouter} from "next/router";
interface Props {
user: User;
@@ -56,11 +58,8 @@ interface Props {
}
export default function TeacherDashboard({user, linkedCorporate}: Props) {
const [page, setPage] = useState("");
const [selectedUser, setSelectedUser] = useState<User>();
const [showModal, setShowModal] = useState(false);
const [selectedAssignment, setSelectedAssignment] = useState<Assignment>();
const [isCreatingAssignment, setIsCreatingAssignment] = useState(false);
const {data: stats} = useFilterRecordsByUser<Stat[]>();
const {users, reload} = useUsers();
@@ -68,6 +67,8 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
const {permissions} = usePermissions(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 assignmentsUsers = useMemo(
@@ -86,8 +87,8 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
);
useEffect(() => {
setShowModal(!!selectedUser && page === "");
}, [selectedUser, page]);
setShowModal(!!selectedUser && router.asPath === "/#");
}, [selectedUser, router.asPath]);
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) => (
<div className="flex flex-col gap-4">
<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">
<BsArrowLeft className="text-xl" />
<span>Back</span>
@@ -141,7 +142,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
<>
<div className="flex flex-col gap-4">
<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">
<BsArrowLeft className="text-xl" />
<span>Back</span>
@@ -179,111 +180,6 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
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 = () => (
<>
{linkedCorporate && (
@@ -297,7 +193,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
!!linkedCorporate && "mt-12 xl:mt-6",
)}>
<IconCard
onClick={() => setPage("students")}
onClick={() => router.push("/#students")}
Icon={BsPersonFill}
label="Students"
value={users.filter(studentFilter).length}
@@ -321,11 +217,11 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) {
label="Groups"
value={groups.filter((x) => x.admin === user.id).length}
color="purple"
onClick={() => setPage("groups")}
onClick={() => router.push("/#groups")}
/>
)}
<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">
<BsEnvelopePaper className="text-6xl text-mti-purple-light" />
<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();
}}
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}
/>
</div>
)}
</>
</Modal>
{page === "students" && <StudentsList />}
{page === "groups" && <GroupsList />}
{page === "assignments" && <AssignmentsPage />}
{page === "" && <DefaultDashboard />}
{router.asPath === "/#students" && <StudentsList />}
{router.asPath === "/#groups" && <GroupsList />}
{router.asPath === "/#assignments" && (
<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 (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));
if (belongingGroupsAdmins.length === 0) return "";