Started the redesign of the dashboard
This commit is contained in:
@@ -34,6 +34,7 @@
|
|||||||
"react-chartjs-2": "^5.2.0",
|
"react-chartjs-2": "^5.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-firebase-hooks": "^5.1.1",
|
"react-firebase-hooks": "^5.1.1",
|
||||||
|
"react-icons": "^4.8.0",
|
||||||
"react-lineto": "^3.3.0",
|
"react-lineto": "^3.3.0",
|
||||||
"react-media-recorder": "^1.6.6",
|
"react-media-recorder": "^1.6.6",
|
||||||
"react-player": "^2.12.0",
|
"react-player": "^2.12.0",
|
||||||
|
|||||||
@@ -1,77 +1,22 @@
|
|||||||
import {Type} from "@/interfaces/user";
|
import {User} from "@/interfaces/user";
|
||||||
import axios from "axios";
|
import {Avatar} from "primereact/avatar";
|
||||||
import Link from "next/link";
|
|
||||||
import {useRouter} from "next/router";
|
|
||||||
import {Button} from "primereact/button";
|
|
||||||
import {Menubar} from "primereact/menubar";
|
|
||||||
import {MenuItem} from "primereact/menuitem";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
profilePicture: string;
|
user: User;
|
||||||
userType: Type;
|
|
||||||
timer?: number;
|
|
||||||
showExamEnd?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable @next/next/no-img-element */
|
/* eslint-disable @next/next/no-img-element */
|
||||||
export default function Navbar({profilePicture, userType, timer, showExamEnd = false}: Props) {
|
export default function Navbar({user}: Props) {
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const logout = async () => {
|
|
||||||
axios.post("/api/logout").finally(() => {
|
|
||||||
router.push("/login");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const items: MenuItem[] = [
|
|
||||||
{
|
|
||||||
label: "Home",
|
|
||||||
icon: "pi pi-fw pi-home",
|
|
||||||
url: "/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Account",
|
|
||||||
icon: "pi pi-fw pi-user",
|
|
||||||
url: "/profile",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Exam",
|
|
||||||
icon: "pi pi-fw pi-plus-circle",
|
|
||||||
url: "/exam",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Users",
|
|
||||||
icon: "pi pi-fw pi-users",
|
|
||||||
items: [
|
|
||||||
...(userType === "student" ? [] : [{label: "List", icon: "pi pi-fw pi-users", url: "/users"}]),
|
|
||||||
{label: "Stats", icon: "pi pi-fw pi-chart-pie", url: "/stats"},
|
|
||||||
{label: "History", icon: "pi pi-fw pi-history", url: "/history"},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Logout",
|
|
||||||
icon: "pi pi-fw pi-power-off",
|
|
||||||
command: logout,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const endTimer = timer && (
|
|
||||||
<span className="pr-2 font-semibold">
|
|
||||||
{Math.floor(timer / 60) < 10 ? "0" : ""}
|
|
||||||
{Math.floor(timer / 60)}:{timer % 60 < 10 ? "0" : ""}
|
|
||||||
{timer % 60}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
const endNewExam = (
|
|
||||||
<Link href="/exam" className="pr-2">
|
|
||||||
<Button text label="Exam" severity="secondary" size="small" />
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-neutral-100 z-10 w-full p-2">
|
<header className="w-full bg-transparent py-4 gap-2 flex items-center">
|
||||||
<Menubar model={items} end={showExamEnd ? endNewExam : endTimer} />
|
<h1 className="font-bold text-2xl w-1/6 px-8">eCrop</h1>
|
||||||
</div>
|
<div className="flex justify-between w-5/6 mr-8">
|
||||||
|
<input type="text" placeholder="Search..." className="rounded-full py-3 px-6 shadow-md outline-none" />
|
||||||
|
<div className="flex gap-3 items-center justify-end">
|
||||||
|
<Avatar size="normal" label={user.name.slice(0, 1)} shape="circle" />
|
||||||
|
<span className="text-right">{user.name}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
66
src/components/Sidebar.tsx
Normal file
66
src/components/Sidebar.tsx
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import clsx from "clsx";
|
||||||
|
import {IconType} from "react-icons";
|
||||||
|
import {MdSpaceDashboard} from "react-icons/md";
|
||||||
|
import {BsFileEarmarkText, BsClockHistory} from "react-icons/bs";
|
||||||
|
import {RiLogoutBoxFill} from "react-icons/ri";
|
||||||
|
import {SlPencil} from "react-icons/sl";
|
||||||
|
import {FaAward} from "react-icons/fa";
|
||||||
|
import Link from "next/link";
|
||||||
|
import {useRouter} from "next/router";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NavProps {
|
||||||
|
Icon: IconType;
|
||||||
|
label: string;
|
||||||
|
path: string;
|
||||||
|
keyPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Nav = ({Icon, label, path, keyPath}: NavProps) => (
|
||||||
|
<Link
|
||||||
|
href={keyPath}
|
||||||
|
className={clsx(
|
||||||
|
"p-4 px-8 rounded-full flex gap-4 items-center cursor-pointer text-black hover:bg-mti-green hover:text-white transition duration-300 ease-in-out",
|
||||||
|
path === keyPath && "bg-mti-green text-white",
|
||||||
|
)}>
|
||||||
|
<Icon size={20} />
|
||||||
|
<span>{label}</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function Sidebar({path}: Props) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const logout = async () => {
|
||||||
|
axios.post("/api/logout").finally(() => {
|
||||||
|
router.push("/login");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="h-full flex bg-transparent flex-col justify-between w-1/6 px-4">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Nav Icon={MdSpaceDashboard} label="Dashboard" path={path} keyPath="/" />
|
||||||
|
<Nav Icon={BsFileEarmarkText} label="Exams" path={path} keyPath="/exam" />
|
||||||
|
<Nav Icon={SlPencil} label="Exercises" path={path} keyPath="/exercise" />
|
||||||
|
<Nav Icon={FaAward} label="Score" path={path} keyPath="/score" />
|
||||||
|
<Nav Icon={BsClockHistory} label="Record" path={path} keyPath="/record" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={1}
|
||||||
|
onClick={logout}
|
||||||
|
className={clsx(
|
||||||
|
"p-4 px-8 rounded-full flex gap-4 items-center cursor-pointer text-black hover:text-mti-orange transition duration-300 ease-in-out",
|
||||||
|
)}>
|
||||||
|
<RiLogoutBoxFill size={20} />
|
||||||
|
<span>Log Out</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -208,10 +208,7 @@ export default function Page() {
|
|||||||
</Head>
|
</Head>
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
{user && (
|
{user && (
|
||||||
<main className="w-full h-full min-h-[100vh] flex flex-col items-center bg-neutral-100 text-black pb-4 gap-4">
|
<main className="w-full h-full min-h-[100vh] flex flex-col items-center bg-neutral-100 text-black pb-4 gap-4">{renderScreen()}</main>
|
||||||
<Navbar userType={user.type} profilePicture={user.profilePicture} timer={exam ? timer : undefined} />
|
|
||||||
{renderScreen()}
|
|
||||||
</main>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -152,7 +152,6 @@ export default function History({user}: {user: User}) {
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
<main className="w-full h-full min-h-[100vh] flex flex-col bg-neutral-100 text-black">
|
<main className="w-full h-full min-h-[100vh] flex flex-col bg-neutral-100 text-black">
|
||||||
<Navbar userType={user.type} profilePicture={user.profilePicture} />
|
|
||||||
<div className="w-fit self-center">
|
<div className="w-fit self-center">
|
||||||
{!isUsersLoading && user.type !== "student" && (
|
{!isUsersLoading && user.type !== "student" && (
|
||||||
<Dropdown value={selectedUser} options={users} optionLabel="name" onChange={(e) => setSelectedUser(e.target.value)} />
|
<Dropdown value={selectedUser} options={users} optionLabel="name" onChange={(e) => setSelectedUser(e.target.value)} />
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import useStats from "@/hooks/useStats";
|
|||||||
import {averageScore, formatModuleTotalStats, totalExams} from "@/utils/stats";
|
import {averageScore, formatModuleTotalStats, totalExams} from "@/utils/stats";
|
||||||
import {Divider} from "primereact/divider";
|
import {Divider} from "primereact/divider";
|
||||||
import useUser from "@/hooks/useUser";
|
import useUser from "@/hooks/useUser";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(({req, res}) => {
|
||||||
const user = req.session.user;
|
const user = req.session.user;
|
||||||
@@ -53,32 +54,11 @@ export default function Home() {
|
|||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
</Head>
|
</Head>
|
||||||
{user && (
|
{user && (
|
||||||
<main className="w-full h-full min-h-[100vh] flex flex-col items-center bg-neutral-100 text-black">
|
<main className="w-full h-full min-h-[100vh] flex flex-col bg-mti-gray text-black">
|
||||||
<Navbar userType={user.type} profilePicture={user.profilePicture} showExamEnd={showEndExam} />
|
<Navbar user={user} />
|
||||||
<div className="w-full h-full p-4 relative flex flex-col gap-8">
|
<div className="h-full w-full flex py-4 gap-2">
|
||||||
<section className="h-full w-full flex lg:gap-8 flex-col lg:flex-row justify-center md:justify-start md:items-start">
|
<Sidebar path="/" />
|
||||||
<section className="w-full h-full flex items-center">
|
<div className="w-5/6 mr-8 bg-white shadow-md rounded-2xl"></div>
|
||||||
<ProfileCard user={user} className="text-black self-start" />
|
|
||||||
</section>
|
|
||||||
{windowWidth <= 960 && <Divider />}
|
|
||||||
<div className="flex flex-col w-full gap-4">
|
|
||||||
<span className="font-bold text-2xl">Statistics</span>
|
|
||||||
{!isLoading && stats && (
|
|
||||||
<div className="text-neutral-600 flex flex-wrap gap-2 md:gap-4 w-full justify-between md:justify-start">
|
|
||||||
<div className="bg-white p-4 rounded-xl drop-shadow-xl flex flex-col gap-2 md:gap-4 w-full">
|
|
||||||
<span className="font-bold text-xl">Exams: {totalExams(stats)}</span>
|
|
||||||
<span className="font-bold text-xl">Exercises: {stats.length}</span>
|
|
||||||
<span className="font-bold text-xl">Average Score: {averageScore(stats)}%</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{!isLoading && stats && (
|
|
||||||
<section className="w-full lg:w-1/3 h-full flex items-center justify-center">
|
|
||||||
<SingleDatasetChart type="polarArea" data={formatModuleTotalStats(stats)} title="Exams per Module" />
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ module.exports = {
|
|||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
|
mti: {
|
||||||
|
orange: "#FF6000",
|
||||||
|
green: "#51C21A",
|
||||||
|
blue: "#007FF8",
|
||||||
|
gray: "#F9F9F9",
|
||||||
|
},
|
||||||
ielts: {
|
ielts: {
|
||||||
reading: {DEFAULT: "#FF6384", transparent: "rgba(255, 99, 132, 0.5)"},
|
reading: {DEFAULT: "#FF6384", transparent: "rgba(255, 99, 132, 0.5)"},
|
||||||
listening: {DEFAULT: "#36A2EB", transparent: "rgba(54, 162, 235, 0.5)"},
|
listening: {DEFAULT: "#36A2EB", transparent: "rgba(54, 162, 235, 0.5)"},
|
||||||
|
|||||||
@@ -3120,6 +3120,11 @@ react-firebase-hooks@^5.1.1:
|
|||||||
resolved "https://registry.npmjs.org/react-firebase-hooks/-/react-firebase-hooks-5.1.1.tgz"
|
resolved "https://registry.npmjs.org/react-firebase-hooks/-/react-firebase-hooks-5.1.1.tgz"
|
||||||
integrity sha512-y2UpWs82xs+39q5Rc/wq316ca52QsC0n8m801V+yM4IC4hbfOL4yQPVSh7w+ydstdvjN9F+lvs1WrO2VYxpmdA==
|
integrity sha512-y2UpWs82xs+39q5Rc/wq316ca52QsC0n8m801V+yM4IC4hbfOL4yQPVSh7w+ydstdvjN9F+lvs1WrO2VYxpmdA==
|
||||||
|
|
||||||
|
react-icons@^4.8.0:
|
||||||
|
version "4.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.8.0.tgz#621e900caa23b912f737e41be57f27f6b2bff445"
|
||||||
|
integrity sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==
|
||||||
|
|
||||||
react-is@^16.13.1, react-is@^16.8.1:
|
react-is@^16.13.1, react-is@^16.8.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user