Added a new card for the Corporate to show their user balance

This commit is contained in:
Tiago Ribeiro
2024-01-20 15:09:42 +00:00
parent 9773f1da72
commit 8eb8a7af46
6 changed files with 75 additions and 6 deletions

View File

@@ -2,7 +2,7 @@
import Modal from "@/components/Modal"; import Modal from "@/components/Modal";
import useStats from "@/hooks/useStats"; import useStats from "@/hooks/useStats";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";
import {Group, Stat, User} from "@/interfaces/user"; import {CorporateUser, Group, Stat, User} from "@/interfaces/user";
import UserList from "@/pages/(admin)/Lists/UserList"; import UserList from "@/pages/(admin)/Lists/UserList";
import {dateSorter} from "@/utils"; import {dateSorter} from "@/utils";
import moment from "moment"; import moment from "moment";
@@ -20,6 +20,9 @@ import {
BsPersonFillGear, BsPersonFillGear,
BsPersonGear, BsPersonGear,
BsPencilSquare, BsPencilSquare,
BsPersonBadge,
BsPersonCheck,
BsPeople,
} from "react-icons/bs"; } from "react-icons/bs";
import UserCard from "@/components/UserCard"; import UserCard from "@/components/UserCard";
import useGroups from "@/hooks/useGroups"; import useGroups from "@/hooks/useGroups";
@@ -31,9 +34,10 @@ import IconCard from "./IconCard";
import GroupList from "@/pages/(admin)/Lists/GroupList"; import GroupList from "@/pages/(admin)/Lists/GroupList";
import useFilterStore from "@/stores/listFilterStore"; import useFilterStore from "@/stores/listFilterStore";
import {useRouter} from "next/router"; import {useRouter} from "next/router";
import useCodes from "@/hooks/useCodes";
interface Props { interface Props {
user: User; user: CorporateUser;
} }
export default function CorporateDashboard({user}: Props) { export default function CorporateDashboard({user}: Props) {
@@ -43,6 +47,7 @@ export default function CorporateDashboard({user}: Props) {
const {stats} = useStats(); const {stats} = useStats();
const {users, reload} = useUsers(); const {users, reload} = useUsers();
const {codes} = useCodes(user.id);
const {groups} = useGroups(user.id); const {groups} = useGroups(user.id);
const appendUserFilters = useFilterStore((state) => state.appendUserFilter); const appendUserFilters = useFilterStore((state) => state.appendUserFilter);
@@ -187,7 +192,13 @@ export default function CorporateDashboard({user}: 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={BsPersonAdd} label="Groups" value={groups.length} color="purple" /> <IconCard onClick={() => setPage("groups")} Icon={BsPeople} label="Groups" value={groups.length} color="purple" />
<IconCard
Icon={BsPersonCheck}
label="User Balance"
value={`${codes.length}/${user.corporateInformation?.companyInformation?.userAmount || 0}`}
color="purple"
/>
<IconCard <IconCard
Icon={BsClock} Icon={BsClock}
label="Expiration Date" label="Expiration Date"

View File

@@ -6,10 +6,11 @@ interface Props {
label: string; label: string;
value: string | number; value: string | number;
color: "purple" | "rose" | "red"; color: "purple" | "rose" | "red";
tooltip?: string;
onClick?: () => void; onClick?: () => void;
} }
export default function IconCard({Icon, label, value, color, onClick}: Props) { export default function IconCard({Icon, label, value, color, tooltip, onClick}: Props) {
const colorClasses: {[key in typeof color]: string} = { const colorClasses: {[key in typeof color]: string} = {
purple: "text-mti-purple-light", purple: "text-mti-purple-light",
red: "text-mti-red-light", red: "text-mti-red-light",
@@ -19,7 +20,11 @@ export default function IconCard({Icon, label, value, color, onClick}: Props) {
return ( return (
<div <div
onClick={onClick} onClick={onClick}
className="bg-white rounded-xl shadow p-4 flex flex-col gap-4 items-center text-center w-52 h-52 justify-center cursor-pointer hover:shadow-xl transition ease-in-out duration-300"> className={clsx(
"bg-white rounded-xl shadow p-4 flex flex-col gap-4 items-center text-center w-52 h-52 justify-center cursor-pointer hover:shadow-xl transition ease-in-out duration-300",
tooltip && "tooltip tooltip-bottom",
)}
data-tip={tooltip}>
<Icon className={clsx("text-6xl", colorClasses[color])} /> <Icon className={clsx("text-6xl", colorClasses[color])} />
<span className="flex flex-col gap-1 items-center text-xl"> <span className="flex flex-col gap-1 items-center text-xl">
<span className="text-lg">{label}</span> <span className="text-lg">{label}</span>

View File

@@ -19,6 +19,7 @@ import {
BsEnvelopePaper, BsEnvelopePaper,
BsGlobeCentralSouthAsia, BsGlobeCentralSouthAsia,
BsPaperclip, BsPaperclip,
BsPeople,
BsPerson, BsPerson,
BsPersonAdd, BsPersonAdd,
BsPersonFill, BsPersonFill,
@@ -271,7 +272,7 @@ export default function TeacherDashboard({user}: 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 Icon={BsPersonAdd} label="Groups" value={groups.length} color="purple" onClick={() => setPage("groups")} /> <IconCard Icon={BsPeople} label="Groups" value={groups.length} color="purple" onClick={() => setPage("groups")} />
<div <div
onClick={() => setPage("assignments")} onClick={() => setPage("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">

21
src/hooks/useCodes.tsx Normal file
View File

@@ -0,0 +1,21 @@
import {Code, Group, User} from "@/interfaces/user";
import axios from "axios";
import {useEffect, useState} from "react";
export default function useCodes(creator?: string) {
const [codes, setCodes] = useState<Code[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const getData = () => {
setIsLoading(true);
axios
.get<Code[]>(`/api/code${creator ? `?creator=${creator}` : ""}`)
.then((response) => setCodes(response.data))
.finally(() => setIsLoading(false));
};
useEffect(getData, [creator]);
return {codes, isLoading, isError, reload: getData};
}

View File

@@ -127,5 +127,16 @@ export interface Group {
disableEditing?: boolean; disableEditing?: boolean;
} }
export interface Code {
code: string;
creator: string;
expiryDate: Date;
type: Type;
userId?: string;
email?: string;
name?: string;
passport_id?: string;
}
export type Type = "student" | "teacher" | "corporate" | "admin" | "developer" | "agent"; export type Type = "student" | "teacher" | "corporate" | "admin" | "developer" | "agent";
export const userTypes: Type[] = ["student", "teacher", "corporate", "admin", "developer", "agent"]; export const userTypes: Type[] = ["student", "teacher", "corporate", "admin", "developer", "agent"];

View File

@@ -14,6 +14,26 @@ const db = getFirestore(app);
export default withIronSessionApiRoute(handler, sessionOptions); export default withIronSessionApiRoute(handler, sessionOptions);
async function handler(req: NextApiRequest, res: NextApiResponse) { async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "GET") return get(req, res);
if (req.method === "POST") return post(req, res);
return res.status(404).json({ok: false});
}
async function get(req: NextApiRequest, res: NextApiResponse) {
if (!req.session.user) {
res.status(401).json({ok: false, reason: "You must be logged in to generate a code!"});
return;
}
const {creator} = req.query as {creator?: string};
const q = query(collection(db, "codes"), where("creator", "==", creator));
const snapshot = await getDocs(creator ? q : collection(db, "codes"));
res.status(200).json(snapshot.docs.map((doc) => doc.data()));
}
async function post(req: NextApiRequest, res: NextApiResponse) {
if (!req.session.user) { if (!req.session.user) {
res.status(401).json({ok: false, reason: "You must be logged in to generate a code!"}); res.status(401).json({ok: false, reason: "You must be logged in to generate a code!"});
return; return;