{selectedCodes.length} code(s) selected
@@ -295,7 +331,7 @@ export default function CodeList({ user }: { user: User }) {
? null
: flexRender(
header.column.columnDef.header,
- header.getContext(),
+ header.getContext()
)}
))}
diff --git a/src/pages/(admin)/Lists/UserList.tsx b/src/pages/(admin)/Lists/UserList.tsx
index e71055c3..dd351f1c 100644
--- a/src/pages/(admin)/Lists/UserList.tsx
+++ b/src/pages/(admin)/Lists/UserList.tsx
@@ -1,625 +1,942 @@
import Button from "@/components/Low/Button";
-import {PERMISSIONS} from "@/constants/userPermissions";
+import { PERMISSIONS } from "@/constants/userPermissions";
import useGroups from "@/hooks/useGroups";
import useUsers from "@/hooks/useUsers";
-import {Type, User, userTypes, CorporateUser, Group} from "@/interfaces/user";
-import {Popover, Transition} from "@headlessui/react";
-import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table";
+import { Type, User, userTypes, CorporateUser, Group } from "@/interfaces/user";
+import { Popover, Transition } from "@headlessui/react";
+import {
+ createColumnHelper,
+ flexRender,
+ getCoreRowModel,
+ useReactTable,
+} from "@tanstack/react-table";
import axios from "axios";
import clsx from "clsx";
-import {capitalize, reverse} from "lodash";
+import { capitalize, reverse } from "lodash";
import moment from "moment";
-import {Fragment, useEffect, useState} from "react";
-import {BsArrowDown, BsArrowDownUp, BsArrowUp, BsCheck, BsCheckCircle, BsEye, BsFillExclamationOctagonFill, BsPerson, BsTrash} from "react-icons/bs";
-import {toast} from "react-toastify";
-import {countries, TCountries} from "countries-list";
+import { Fragment, useEffect, useState } from "react";
+import {
+ BsArrowDown,
+ BsArrowDownUp,
+ BsArrowUp,
+ BsCheck,
+ BsCheckCircle,
+ BsEye,
+ BsFillExclamationOctagonFill,
+ BsPerson,
+ BsTrash,
+} from "react-icons/bs";
+import { toast } from "react-toastify";
+import { countries, TCountries } from "countries-list";
import countryCodes from "country-codes-list";
import Modal from "@/components/Modal";
import UserCard from "@/components/UserCard";
-import {getUserCompanyName, isAgentUser, USER_TYPE_LABELS} from "@/resources/user";
+import {
+ getUserCompanyName,
+ isAgentUser,
+ USER_TYPE_LABELS,
+} from "@/resources/user";
import useFilterStore from "@/stores/listFilterStore";
-import {useRouter} from "next/router";
-import {isCorporateUser} from "@/resources/user";
-import {useListSearch} from "@/hooks/useListSearch";
-import {getUserCorporate} from "@/utils/groups";
-import {asyncSorter} from "@/utils";
-import {exportListToExcel, UserListRow} from "@/utils/users";
+import { useRouter } from "next/router";
+import { isCorporateUser } from "@/resources/user";
+import { useListSearch } from "@/hooks/useListSearch";
+import { getUserCorporate } from "@/utils/groups";
+import { asyncSorter } from "@/utils";
+import { exportListToExcel, UserListRow } from "@/utils/users";
const columnHelper = createColumnHelper
();
-const searchFields = [["name"], ["email"], ["corporateInformation", "companyInformation", "name"]];
+const searchFields = [
+ ["name"],
+ ["email"],
+ ["corporateInformation", "companyInformation", "name"],
+];
-const CompanyNameCell = ({users, user, groups}: {user: User; users: User[]; groups: Group[]}) => {
- const [companyName, setCompanyName] = useState("");
- const [isLoading, setIsLoading] = useState(false);
+const CompanyNameCell = ({
+ users,
+ user,
+ groups,
+}: {
+ user: User;
+ users: User[];
+ groups: Group[];
+}) => {
+ const [companyName, setCompanyName] = useState("");
+ const [isLoading, setIsLoading] = useState(false);
- useEffect(() => {
- const name = getUserCompanyName(user, users, groups);
- setCompanyName(name);
- }, [user, users, groups]);
+ useEffect(() => {
+ const name = getUserCompanyName(user, users, groups);
+ setCompanyName(name);
+ }, [user, users, groups]);
- return isLoading ? Loading... : <>{companyName}>;
+ return isLoading ? (
+ Loading...
+ ) : (
+ <>{companyName}>
+ );
};
-export default function UserList({user, filters = []}: {user: User; filters?: ((user: User) => boolean)[]}) {
- const [showDemographicInformation, setShowDemographicInformation] = useState(false);
- const [sorter, setSorter] = useState();
- const [displayUsers, setDisplayUsers] = useState([]);
- const [selectedUser, setSelectedUser] = useState();
+export default function UserList({
+ user,
+ filters = [],
+ renderHeader,
+}: {
+ user: User;
+ filters?: ((user: User) => boolean)[];
+ renderHeader?: (total: number) => JSX.Element;
+}) {
+ const [showDemographicInformation, setShowDemographicInformation] =
+ useState(false);
+ const [sorter, setSorter] = useState();
+ const [displayUsers, setDisplayUsers] = useState([]);
+ const [selectedUser, setSelectedUser] = useState();
- const {users, reload} = useUsers();
- const {groups} = useGroups(user && (user?.type === "corporate" || user?.type === "teacher") ? user.id : undefined);
+ const { users, reload } = useUsers();
+ const { groups } = useGroups(
+ user && (user?.type === "corporate" || user?.type === "teacher")
+ ? user.id
+ : undefined
+ );
- const appendUserFilters = useFilterStore((state) => state.appendUserFilter);
- const router = useRouter();
+ const appendUserFilters = useFilterStore((state) => state.appendUserFilter);
+ const router = useRouter();
- const expirationDateColor = (date: Date) => {
- const momentDate = moment(date);
- const today = moment(new Date());
+ const expirationDateColor = (date: Date) => {
+ const momentDate = moment(date);
+ const today = moment(new Date());
- if (today.isAfter(momentDate)) return "!text-mti-red-light font-bold line-through";
- if (today.add(1, "weeks").isAfter(momentDate)) return "!text-mti-red-light";
- if (today.add(2, "weeks").isAfter(momentDate)) return "!text-mti-rose-light";
- if (today.add(1, "months").isAfter(momentDate)) return "!text-mti-orange-light";
- };
+ if (today.isAfter(momentDate))
+ return "!text-mti-red-light font-bold line-through";
+ if (today.add(1, "weeks").isAfter(momentDate)) return "!text-mti-red-light";
+ if (today.add(2, "weeks").isAfter(momentDate))
+ return "!text-mti-rose-light";
+ if (today.add(1, "months").isAfter(momentDate))
+ return "!text-mti-orange-light";
+ };
- useEffect(() => {
- (async () => {
- if (user && users) {
- const filterUsers =
- user.type === "corporate" || user.type === "teacher"
- ? users.filter((u) => groups.flatMap((g) => g.participants).includes(u.id))
- : users;
+ useEffect(() => {
+ (async () => {
+ if (user && users) {
+ const filterUsers =
+ user.type === "corporate" || user.type === "teacher"
+ ? users.filter((u) =>
+ groups.flatMap((g) => g.participants).includes(u.id)
+ )
+ : users;
- const filteredUsers = filters.reduce((d, f) => d.filter(f), filterUsers);
- const sortedUsers = await asyncSorter(filteredUsers, sortFunction);
- console.log(sortedUsers);
+ const filteredUsers = filters.reduce(
+ (d, f) => d.filter(f),
+ filterUsers
+ );
+ const sortedUsers = await asyncSorter(
+ filteredUsers,
+ sortFunction
+ );
+ console.log(sortedUsers);
- setDisplayUsers([...sortedUsers]);
- }
- })();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [user, users, sorter, groups]);
+ setDisplayUsers([...sortedUsers]);
+ }
+ })();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [user, users, sorter, groups]);
- const deleteAccount = (user: User) => {
- if (!confirm(`Are you sure you want to delete ${user.name}'s account?`)) return;
+ const deleteAccount = (user: User) => {
+ if (!confirm(`Are you sure you want to delete ${user.name}'s account?`))
+ return;
- axios
- .delete<{ok: boolean}>(`/api/user?id=${user.id}`)
- .then(() => {
- toast.success("User deleted successfully!");
- reload();
- })
- .catch(() => {
- toast.error("Something went wrong!", {toastId: "delete-error"});
- })
- .finally(reload);
- };
+ axios
+ .delete<{ ok: boolean }>(`/api/user?id=${user.id}`)
+ .then(() => {
+ toast.success("User deleted successfully!");
+ reload();
+ })
+ .catch(() => {
+ toast.error("Something went wrong!", { toastId: "delete-error" });
+ })
+ .finally(reload);
+ };
- const updateAccountType = (user: User, type: Type) => {
- if (!confirm(`Are you sure you want to update ${user.name}'s account from ${capitalize(user.type)} to ${capitalize(type)}?`)) return;
+ const updateAccountType = (user: User, type: Type) => {
+ if (
+ !confirm(
+ `Are you sure you want to update ${
+ user.name
+ }'s account from ${capitalize(user.type)} to ${capitalize(type)}?`
+ )
+ )
+ return;
- axios
- .post<{user?: User; ok?: boolean}>(`/api/users/update?id=${user.id}`, {...user, type})
- .then(() => {
- toast.success("User type updated successfully!");
- reload();
- })
- .catch(() => {
- toast.error("Something went wrong!", {toastId: "update-error"});
- });
- };
+ axios
+ .post<{ user?: User; ok?: boolean }>(`/api/users/update?id=${user.id}`, {
+ ...user,
+ type,
+ })
+ .then(() => {
+ toast.success("User type updated successfully!");
+ reload();
+ })
+ .catch(() => {
+ toast.error("Something went wrong!", { toastId: "update-error" });
+ });
+ };
- const verifyAccount = (user: User) => {
- axios
- .post<{user?: User; ok?: boolean}>(`/api/users/update?id=${user.id}`, {...user, isVerified: true})
- .then(() => {
- toast.success("User verified successfully!");
- reload();
- })
- .catch(() => {
- toast.error("Something went wrong!", {toastId: "update-error"});
- });
- };
+ const verifyAccount = (user: User) => {
+ axios
+ .post<{ user?: User; ok?: boolean }>(`/api/users/update?id=${user.id}`, {
+ ...user,
+ isVerified: true,
+ })
+ .then(() => {
+ toast.success("User verified successfully!");
+ reload();
+ })
+ .catch(() => {
+ toast.error("Something went wrong!", { toastId: "update-error" });
+ });
+ };
- const toggleDisableAccount = (user: User) => {
- if (
- !confirm(
- `Are you sure you want to ${user.status === "disabled" ? "enable" : "disable"} ${
- user.name
- }'s account? This change is usually related to their payment state.`,
- )
- )
- return;
+ const toggleDisableAccount = (user: User) => {
+ if (
+ !confirm(
+ `Are you sure you want to ${
+ user.status === "disabled" ? "enable" : "disable"
+ } ${
+ user.name
+ }'s account? This change is usually related to their payment state.`
+ )
+ )
+ return;
- axios
- .post<{user?: User; ok?: boolean}>(`/api/users/update?id=${user.id}`, {
- ...user,
- status: user.status === "disabled" ? "active" : "disabled",
- })
- .then(() => {
- toast.success(`User ${user.status === "disabled" ? "enabled" : "disabled"} successfully!`);
- reload();
- })
- .catch(() => {
- toast.error("Something went wrong!", {toastId: "update-error"});
- });
- };
+ axios
+ .post<{ user?: User; ok?: boolean }>(`/api/users/update?id=${user.id}`, {
+ ...user,
+ status: user.status === "disabled" ? "active" : "disabled",
+ })
+ .then(() => {
+ toast.success(
+ `User ${
+ user.status === "disabled" ? "enabled" : "disabled"
+ } successfully!`
+ );
+ reload();
+ })
+ .catch(() => {
+ toast.error("Something went wrong!", { toastId: "update-error" });
+ });
+ };
- const SorterArrow = ({name}: {name: string}) => {
- if (sorter === name) return ;
- if (sorter === reverseString(name)) return ;
+ const SorterArrow = ({ name }: { name: string }) => {
+ if (sorter === name) return ;
+ if (sorter === reverseString(name)) return ;
- return ;
- };
+ return ;
+ };
- const actionColumn = ({row}: {row: {original: User}}) => {
- return (
-
- {PERMISSIONS.updateUser[row.original.type].includes(user.type) && (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
- {!row.original.isVerified && PERMISSIONS.updateUser[row.original.type].includes(user.type) && (
-
verifyAccount(row.original)}>
-
-
- )}
- {PERMISSIONS.updateUser[row.original.type].includes(user.type) && (
-
toggleDisableAccount(row.original)}>
- {row.original.status === "disabled" ? (
-
- ) : (
-
- )}
-
- )}
- {PERMISSIONS.deleteUser[row.original.type].includes(user.type) && (
-
deleteAccount(row.original)}>
-
-
- )}
-
- );
- };
+ const actionColumn = ({ row }: { row: { original: User } }) => {
+ return (
+
+ {PERMISSIONS.updateUser[row.original.type].includes(user.type) && (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ {!row.original.isVerified &&
+ PERMISSIONS.updateUser[row.original.type].includes(user.type) && (
+
verifyAccount(row.original)}
+ >
+
+
+ )}
+ {PERMISSIONS.updateUser[row.original.type].includes(user.type) && (
+
toggleDisableAccount(row.original)}
+ >
+ {row.original.status === "disabled" ? (
+
+ ) : (
+
+ )}
+
+ )}
+ {PERMISSIONS.deleteUser[row.original.type].includes(user.type) && (
+
deleteAccount(row.original)}
+ >
+
+
+ )}
+
+ );
+ };
- const demographicColumns = [
- columnHelper.accessor("name", {
- header: (
-
- ) as any,
- cell: ({row, getValue}) => (
- (PERMISSIONS.updateExpiryDate[row.original.type].includes(user.type) ? setSelectedUser(row.original) : null)}>
- {getValue()}
-
- ),
- }),
- columnHelper.accessor("demographicInformation.country", {
- header: (
-
- ) as any,
- cell: (info) =>
- info.getValue()
- ? `${countryCodes.findOne("countryCode" as any, info.getValue()).flag} ${
- countries[info.getValue() as unknown as keyof TCountries].name
- } (+${countryCodes.findOne("countryCode" as any, info.getValue()).countryCallingCode})`
- : "Not available",
- }),
- columnHelper.accessor("demographicInformation.phone", {
- header: (
-
- ) as any,
- cell: (info) => info.getValue() || "Not available",
- enableSorting: true,
- }),
- columnHelper.accessor((x) => (x.type === "corporate" ? x.demographicInformation?.position : x.demographicInformation?.employment), {
- id: "employment",
- header: (
-
- ) as any,
- cell: (info) => (info.row.original.type === "corporate" ? info.getValue() : capitalize(info.getValue())) || "Not available",
- enableSorting: true,
- }),
- columnHelper.accessor("demographicInformation.gender", {
- header: (
-
- ) as any,
- cell: (info) => capitalize(info.getValue()) || "Not available",
- enableSorting: true,
- }),
- {
- header: (
- setShowDemographicInformation((prev) => !prev)}>
- Switch
-
- ),
- id: "actions",
- cell: actionColumn,
- },
- ];
+ const demographicColumns = [
+ columnHelper.accessor("name", {
+ header: (
+
+ ) as any,
+ cell: ({ row, getValue }) => (
+
+ PERMISSIONS.updateExpiryDate[row.original.type].includes(user.type)
+ ? setSelectedUser(row.original)
+ : null
+ }
+ >
+ {getValue()}
+
+ ),
+ }),
+ columnHelper.accessor("demographicInformation.country", {
+ header: (
+
+ ) as any,
+ cell: (info) =>
+ info.getValue()
+ ? `${
+ countryCodes.findOne("countryCode" as any, info.getValue()).flag
+ } ${
+ countries[info.getValue() as unknown as keyof TCountries].name
+ } (+${
+ countryCodes.findOne("countryCode" as any, info.getValue())
+ .countryCallingCode
+ })`
+ : "Not available",
+ }),
+ columnHelper.accessor("demographicInformation.phone", {
+ header: (
+
+ ) as any,
+ cell: (info) => info.getValue() || "Not available",
+ enableSorting: true,
+ }),
+ columnHelper.accessor(
+ (x) =>
+ x.type === "corporate"
+ ? x.demographicInformation?.position
+ : x.demographicInformation?.employment,
+ {
+ id: "employment",
+ header: (
+
+ ) as any,
+ cell: (info) =>
+ (info.row.original.type === "corporate"
+ ? info.getValue()
+ : capitalize(info.getValue())) || "Not available",
+ enableSorting: true,
+ }
+ ),
+ columnHelper.accessor("demographicInformation.gender", {
+ header: (
+
+ ) as any,
+ cell: (info) => capitalize(info.getValue()) || "Not available",
+ enableSorting: true,
+ }),
+ {
+ header: (
+ setShowDemographicInformation((prev) => !prev)}
+ >
+ Switch
+
+ ),
+ id: "actions",
+ cell: actionColumn,
+ },
+ ];
- const defaultColumns = [
- columnHelper.accessor("name", {
- header: (
-
- ) as any,
- cell: ({row, getValue}) => (
- (PERMISSIONS.updateExpiryDate[row.original.type].includes(user.type) ? setSelectedUser(row.original) : null)}>
- {row.original.type === "corporate" ? row.original.corporateInformation?.companyInformation?.name || getValue() : getValue()}
-
- ),
- }),
- columnHelper.accessor("email", {
- header: (
-
- ) as any,
- cell: ({row, getValue}) => (
- (PERMISSIONS.updateExpiryDate[row.original.type].includes(user.type) ? setSelectedUser(row.original) : null)}>
- {getValue()}
-
- ),
- }),
- columnHelper.accessor("type", {
- header: (
-
- ) as any,
- cell: (info) => USER_TYPE_LABELS[info.getValue()],
- }),
- columnHelper.accessor("corporateInformation.companyInformation.name", {
- header: (
-
- ) as any,
- cell: (info) => ,
- }),
- columnHelper.accessor("subscriptionExpirationDate", {
- header: (
-
- ) as any,
- cell: (info) => (
-
- {!info.getValue() ? "No expiry date" : moment(info.getValue()).format("DD/MM/YYYY")}
-
- ),
- }),
- columnHelper.accessor("isVerified", {
- header: (
-
- ) as any,
- cell: (info) => (
-
- ),
- }),
- {
- header: (
- setShowDemographicInformation((prev) => !prev)}>
- Switch
-
- ),
- id: "actions",
- cell: actionColumn,
- },
- ];
+ const defaultColumns = [
+ columnHelper.accessor("name", {
+ header: (
+
+ ) as any,
+ cell: ({ row, getValue }) => (
+
+ PERMISSIONS.updateExpiryDate[row.original.type].includes(user.type)
+ ? setSelectedUser(row.original)
+ : null
+ }
+ >
+ {row.original.type === "corporate"
+ ? row.original.corporateInformation?.companyInformation?.name ||
+ getValue()
+ : getValue()}
+
+ ),
+ }),
+ columnHelper.accessor("email", {
+ header: (
+
+ ) as any,
+ cell: ({ row, getValue }) => (
+
+ PERMISSIONS.updateExpiryDate[row.original.type].includes(user.type)
+ ? setSelectedUser(row.original)
+ : null
+ }
+ >
+ {getValue()}
+
+ ),
+ }),
+ columnHelper.accessor("type", {
+ header: (
+
+ ) as any,
+ cell: (info) => USER_TYPE_LABELS[info.getValue()],
+ }),
+ columnHelper.accessor("corporateInformation.companyInformation.name", {
+ header: (
+
+ ) as any,
+ cell: (info) => (
+
+ ),
+ }),
+ columnHelper.accessor("subscriptionExpirationDate", {
+ header: (
+
+ ) as any,
+ cell: (info) => (
+
+ {!info.getValue()
+ ? "No expiry date"
+ : moment(info.getValue()).format("DD/MM/YYYY")}
+
+ ),
+ }),
+ columnHelper.accessor("isVerified", {
+ header: (
+
+ ) as any,
+ cell: (info) => (
+
+ ),
+ }),
+ {
+ header: (
+ setShowDemographicInformation((prev) => !prev)}
+ >
+ Switch
+
+ ),
+ id: "actions",
+ cell: actionColumn,
+ },
+ ];
- const reverseString = (str: string) => reverse(str.split("")).join("");
+ const reverseString = (str: string) => reverse(str.split("")).join("");
- const selectSorter = (previous: string | undefined, name: string) => {
- if (!previous) return name;
- if (previous === name) return reverseString(name);
+ const selectSorter = (previous: string | undefined, name: string) => {
+ if (!previous) return name;
+ if (previous === name) return reverseString(name);
- return undefined;
- };
+ return undefined;
+ };
- const sortFunction = async (a: User, b: User) => {
- if (sorter === "name" || sorter === reverseString("name"))
- return sorter === "name" ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
+ const sortFunction = async (a: User, b: User) => {
+ if (sorter === "name" || sorter === reverseString("name"))
+ return sorter === "name"
+ ? a.name.localeCompare(b.name)
+ : b.name.localeCompare(a.name);
- if (sorter === "email" || sorter === reverseString("email"))
- return sorter === "email" ? a.email.localeCompare(b.email) : b.email.localeCompare(a.email);
+ if (sorter === "email" || sorter === reverseString("email"))
+ return sorter === "email"
+ ? a.email.localeCompare(b.email)
+ : b.email.localeCompare(a.email);
- if (sorter === "type" || sorter === reverseString("type"))
- return sorter === "type"
- ? userTypes.findIndex((t) => a.type === t) - userTypes.findIndex((t) => b.type === t)
- : userTypes.findIndex((t) => b.type === t) - userTypes.findIndex((t) => a.type === t);
+ if (sorter === "type" || sorter === reverseString("type"))
+ return sorter === "type"
+ ? userTypes.findIndex((t) => a.type === t) -
+ userTypes.findIndex((t) => b.type === t)
+ : userTypes.findIndex((t) => b.type === t) -
+ userTypes.findIndex((t) => a.type === t);
- if (sorter === "verification" || sorter === reverseString("verification"))
- return sorter === "verification"
- ? a.isVerified.toString().localeCompare(b.isVerified.toString())
- : b.isVerified.toString().localeCompare(a.isVerified.toString());
+ if (sorter === "verification" || sorter === reverseString("verification"))
+ return sorter === "verification"
+ ? a.isVerified.toString().localeCompare(b.isVerified.toString())
+ : b.isVerified.toString().localeCompare(a.isVerified.toString());
- if (sorter === "expiryDate" || sorter === reverseString("expiryDate")) {
- if (!a.subscriptionExpirationDate && b.subscriptionExpirationDate) return sorter === "expiryDate" ? -1 : 1;
- if (a.subscriptionExpirationDate && !b.subscriptionExpirationDate) return sorter === "expiryDate" ? 1 : -1;
- if (!a.subscriptionExpirationDate && !b.subscriptionExpirationDate) return 0;
- if (moment(a.subscriptionExpirationDate).isAfter(b.subscriptionExpirationDate)) return sorter === "expiryDate" ? -1 : 1;
- if (moment(b.subscriptionExpirationDate).isAfter(a.subscriptionExpirationDate)) return sorter === "expiryDate" ? 1 : -1;
- return 0;
- }
+ if (sorter === "expiryDate" || sorter === reverseString("expiryDate")) {
+ if (!a.subscriptionExpirationDate && b.subscriptionExpirationDate)
+ return sorter === "expiryDate" ? -1 : 1;
+ if (a.subscriptionExpirationDate && !b.subscriptionExpirationDate)
+ return sorter === "expiryDate" ? 1 : -1;
+ if (!a.subscriptionExpirationDate && !b.subscriptionExpirationDate)
+ return 0;
+ if (
+ moment(a.subscriptionExpirationDate).isAfter(
+ b.subscriptionExpirationDate
+ )
+ )
+ return sorter === "expiryDate" ? -1 : 1;
+ if (
+ moment(b.subscriptionExpirationDate).isAfter(
+ a.subscriptionExpirationDate
+ )
+ )
+ return sorter === "expiryDate" ? 1 : -1;
+ return 0;
+ }
- if (sorter === "country" || sorter === reverseString("country")) {
- if (!a.demographicInformation?.country && b.demographicInformation?.country) return sorter === "country" ? -1 : 1;
- if (a.demographicInformation?.country && !b.demographicInformation?.country) return sorter === "country" ? 1 : -1;
- if (!a.demographicInformation?.country && !b.demographicInformation?.country) return 0;
+ if (sorter === "country" || sorter === reverseString("country")) {
+ if (
+ !a.demographicInformation?.country &&
+ b.demographicInformation?.country
+ )
+ return sorter === "country" ? -1 : 1;
+ if (
+ a.demographicInformation?.country &&
+ !b.demographicInformation?.country
+ )
+ return sorter === "country" ? 1 : -1;
+ if (
+ !a.demographicInformation?.country &&
+ !b.demographicInformation?.country
+ )
+ return 0;
- return sorter === "country"
- ? a.demographicInformation!.country.localeCompare(b.demographicInformation!.country)
- : b.demographicInformation!.country.localeCompare(a.demographicInformation!.country);
- }
+ return sorter === "country"
+ ? a.demographicInformation!.country.localeCompare(
+ b.demographicInformation!.country
+ )
+ : b.demographicInformation!.country.localeCompare(
+ a.demographicInformation!.country
+ );
+ }
- if (sorter === "phone" || sorter === reverseString("phone")) {
- if (!a.demographicInformation?.phone && b.demographicInformation?.phone) return sorter === "phone" ? -1 : 1;
- if (a.demographicInformation?.phone && !b.demographicInformation?.phone) return sorter === "phone" ? 1 : -1;
- if (!a.demographicInformation?.phone && !b.demographicInformation?.phone) return 0;
+ if (sorter === "phone" || sorter === reverseString("phone")) {
+ if (!a.demographicInformation?.phone && b.demographicInformation?.phone)
+ return sorter === "phone" ? -1 : 1;
+ if (a.demographicInformation?.phone && !b.demographicInformation?.phone)
+ return sorter === "phone" ? 1 : -1;
+ if (!a.demographicInformation?.phone && !b.demographicInformation?.phone)
+ return 0;
- return sorter === "phone"
- ? a.demographicInformation!.phone.localeCompare(b.demographicInformation!.phone)
- : b.demographicInformation!.phone.localeCompare(a.demographicInformation!.phone);
- }
+ return sorter === "phone"
+ ? a.demographicInformation!.phone.localeCompare(
+ b.demographicInformation!.phone
+ )
+ : b.demographicInformation!.phone.localeCompare(
+ a.demographicInformation!.phone
+ );
+ }
- if (sorter === "employment" || sorter === reverseString("employment")) {
- const aSortingItem = a.type === "corporate" ? a.demographicInformation?.position : a.demographicInformation?.employment;
- const bSortingItem = b.type === "corporate" ? b.demographicInformation?.position : b.demographicInformation?.employment;
+ if (sorter === "employment" || sorter === reverseString("employment")) {
+ const aSortingItem =
+ a.type === "corporate"
+ ? a.demographicInformation?.position
+ : a.demographicInformation?.employment;
+ const bSortingItem =
+ b.type === "corporate"
+ ? b.demographicInformation?.position
+ : b.demographicInformation?.employment;
- if (!aSortingItem && bSortingItem) return sorter === "employment" ? -1 : 1;
- if (aSortingItem && !bSortingItem) return sorter === "employment" ? 1 : -1;
- if (!aSortingItem && !bSortingItem) return 0;
+ if (!aSortingItem && bSortingItem)
+ return sorter === "employment" ? -1 : 1;
+ if (aSortingItem && !bSortingItem)
+ return sorter === "employment" ? 1 : -1;
+ if (!aSortingItem && !bSortingItem) return 0;
- return sorter === "employment" ? aSortingItem!.localeCompare(bSortingItem!) : bSortingItem!.localeCompare(aSortingItem!);
- }
+ return sorter === "employment"
+ ? aSortingItem!.localeCompare(bSortingItem!)
+ : bSortingItem!.localeCompare(aSortingItem!);
+ }
- if (sorter === "gender" || sorter === reverseString("gender")) {
- if (!a.demographicInformation?.gender && b.demographicInformation?.gender) return sorter === "employment" ? -1 : 1;
- if (a.demographicInformation?.gender && !b.demographicInformation?.gender) return sorter === "employment" ? 1 : -1;
- if (!a.demographicInformation?.gender && !b.demographicInformation?.gender) return 0;
+ if (sorter === "gender" || sorter === reverseString("gender")) {
+ if (!a.demographicInformation?.gender && b.demographicInformation?.gender)
+ return sorter === "employment" ? -1 : 1;
+ if (a.demographicInformation?.gender && !b.demographicInformation?.gender)
+ return sorter === "employment" ? 1 : -1;
+ if (
+ !a.demographicInformation?.gender &&
+ !b.demographicInformation?.gender
+ )
+ return 0;
- return sorter === "gender"
- ? a.demographicInformation!.gender.localeCompare(b.demographicInformation!.gender)
- : b.demographicInformation!.gender.localeCompare(a.demographicInformation!.gender);
- }
+ return sorter === "gender"
+ ? a.demographicInformation!.gender.localeCompare(
+ b.demographicInformation!.gender
+ )
+ : b.demographicInformation!.gender.localeCompare(
+ a.demographicInformation!.gender
+ );
+ }
- if (sorter === "companyName" || sorter === reverseString("companyName")) {
- const aCorporateName = getUserCompanyName(a, users, groups);
- const bCorporateName = getUserCompanyName(b, users, groups);
- if (!aCorporateName && bCorporateName) return sorter === "companyName" ? -1 : 1;
- if (aCorporateName && !bCorporateName) return sorter === "companyName" ? 1 : -1;
- if (!aCorporateName && !bCorporateName) return 0;
+ if (sorter === "companyName" || sorter === reverseString("companyName")) {
+ const aCorporateName = getUserCompanyName(a, users, groups);
+ const bCorporateName = getUserCompanyName(b, users, groups);
+ if (!aCorporateName && bCorporateName)
+ return sorter === "companyName" ? -1 : 1;
+ if (aCorporateName && !bCorporateName)
+ return sorter === "companyName" ? 1 : -1;
+ if (!aCorporateName && !bCorporateName) return 0;
- return sorter === "companyName" ? aCorporateName.localeCompare(bCorporateName) : bCorporateName.localeCompare(aCorporateName);
- }
+ return sorter === "companyName"
+ ? aCorporateName.localeCompare(bCorporateName)
+ : bCorporateName.localeCompare(aCorporateName);
+ }
- return a.id.localeCompare(b.id);
- };
+ return a.id.localeCompare(b.id);
+ };
- const {rows: filteredRows, renderSearch} = useListSearch(searchFields, displayUsers);
+ const { rows: filteredRows, renderSearch } = useListSearch(
+ searchFields,
+ displayUsers
+ );
- const table = useReactTable({
- data: filteredRows,
- columns: (!showDemographicInformation ? defaultColumns : demographicColumns) as any,
- getCoreRowModel: getCoreRowModel(),
- });
+ const table = useReactTable({
+ data: filteredRows,
+ columns: (!showDemographicInformation
+ ? defaultColumns
+ : demographicColumns) as any,
+ getCoreRowModel: getCoreRowModel(),
+ });
- const downloadExcel = () => {
- const csv = exportListToExcel(filteredRows, users, groups);
+ const downloadExcel = () => {
+ const csv = exportListToExcel(filteredRows, users, groups);
- const element = document.createElement("a");
- const file = new Blob([csv], {type: "text/plain"});
- element.href = URL.createObjectURL(file);
- element.download = "users.xlsx";
- document.body.appendChild(element);
- element.click();
- document.body.removeChild(element);
- };
+ const element = document.createElement("a");
+ const file = new Blob([csv], { type: "text/csv" });
+ element.href = URL.createObjectURL(file);
+ element.download = "users.csv";
+ document.body.appendChild(element);
+ element.click();
+ document.body.removeChild(element);
+ };
- return (
-
-
setSelectedUser(undefined)}>
- <>
- {selectedUser && (
-
-
{
- appendUserFilters({
- id: "view-students",
- filter: (x: User) => x.type === "student",
- });
- appendUserFilters({
- id: "belongs-to-admin",
- filter: (x: User) =>
- groups
- .filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id))
- .flatMap((g) => g.participants)
- .includes(x.id),
- });
+ const viewStudentFilter = (x: User) => x.type === "student";
+ const viewTeacherFilter = (x: User) => x.type === "teacher";
+ const belongsToAdminFilter = (x: User) => {
+ if (!selectedUser) return false;
+ return groups
+ .filter(
+ (g) =>
+ g.admin === selectedUser.id ||
+ g.participants.includes(selectedUser.id)
+ )
+ .flatMap((g) => g.participants)
+ .includes(x.id);
+ };
- router.push("/list/users");
- }
- : undefined
- }
- onViewTeachers={
- selectedUser.type === "corporate" || selectedUser.type === "student"
- ? () => {
- appendUserFilters({
- id: "view-teachers",
- filter: (x: User) => x.type === "teacher",
- });
- appendUserFilters({
- id: "belongs-to-admin",
- filter: (x: User) =>
- groups
- .filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id))
- .flatMap((g) => g.participants)
- .includes(x.id),
- });
+ const viewStudentFilterBelongsToAdmin = (x: User) =>
+ x.type === "student" && belongsToAdminFilter(x);
+ const viewTeacherFilterBelongsToAdmin = (x: User) =>
+ x.type === "teacher" && belongsToAdminFilter(x);
- router.push("/list/users");
- }
- : undefined
- }
- onViewCorporate={
- selectedUser.type === "teacher" || selectedUser.type === "student"
- ? () => {
- appendUserFilters({
- id: "view-corporate",
- filter: (x: User) => x.type === "corporate",
- });
- appendUserFilters({
- id: "belongs-to-admin",
- filter: (x: User) =>
- groups
- .filter((g) => g.participants.includes(selectedUser.id))
- .flatMap((g) => [g.admin, ...g.participants])
- .includes(x.id),
- });
+ const renderUserCard = (selectedUser: User) => {
+ const studentsFromAdmin = users.filter(viewStudentFilterBelongsToAdmin);
+ const teachersFromAdmin = users.filter(viewTeacherFilterBelongsToAdmin);
+ return (
+
+ 0
+ ? () => {
+ appendUserFilters({
+ id: "view-students",
+ filter: viewStudentFilter,
+ });
+ appendUserFilters({
+ id: "belongs-to-admin",
+ filter: belongsToAdminFilter,
+ });
- router.push("/list/users");
- }
- : undefined
- }
- onClose={(shouldReload) => {
- setSelectedUser(undefined);
- if (shouldReload) reload();
- }}
- user={selectedUser}
- />
-
- )}
- >
-
-
-
- {renderSearch()}
-
-
-
-
- {table.getHeaderGroups().map((headerGroup) => (
-
- {headerGroup.headers.map((header) => (
- |
- {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
- |
- ))}
-
- ))}
-
-
- {table.getRowModel().rows.map((row) => (
-
- {row.getVisibleCells().map((cell) => (
- |
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
- |
- ))}
-
- ))}
-
-
-
-
- );
+ router.push("/list/users");
+ }
+ : undefined
+ }
+ onViewTeachers={
+ (selectedUser.type === "corporate" ||
+ selectedUser.type === "student") &&
+ teachersFromAdmin.length > 0
+ ? () => {
+ appendUserFilters({
+ id: "view-teachers",
+ filter: viewTeacherFilter,
+ });
+ appendUserFilters({
+ id: "belongs-to-admin",
+ filter: belongsToAdminFilter,
+ });
+
+ router.push("/list/users");
+ }
+ : undefined
+ }
+ onViewCorporate={
+ selectedUser.type === "teacher" || selectedUser.type === "student"
+ ? () => {
+ appendUserFilters({
+ id: "view-corporate",
+ filter: (x: User) => x.type === "corporate",
+ });
+ appendUserFilters({
+ id: "belongs-to-admin",
+ filter: (x: User) =>
+ groups
+ .filter((g) => g.participants.includes(selectedUser.id))
+ .flatMap((g) => [g.admin, ...g.participants])
+ .includes(x.id),
+ });
+
+ router.push("/list/users");
+ }
+ : undefined
+ }
+ onClose={(shouldReload) => {
+ setSelectedUser(undefined);
+ if (shouldReload) reload();
+ }}
+ user={selectedUser}
+ />
+
+ );
+ };
+
+ return (
+ <>
+ {renderHeader && renderHeader(displayUsers.length)}
+
+
setSelectedUser(undefined)}
+ >
+ {selectedUser && renderUserCard(selectedUser)}
+
+
+
+ {renderSearch()}
+
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+ |
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+ |
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+ |
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
+ )}
+ |
+ ))}
+
+ ))}
+
+
+
+
+ >
+ );
}
diff --git a/src/pages/list/users.tsx b/src/pages/list/users.tsx
index 3ec4c585..a04193b8 100644
--- a/src/pages/list/users.tsx
+++ b/src/pages/list/users.tsx
@@ -1,77 +1,85 @@
import Layout from "@/components/High/Layout";
import useUser from "@/hooks/useUser";
import useUsers from "@/hooks/useUsers";
-import {sessionOptions} from "@/lib/session";
+import { sessionOptions } from "@/lib/session";
import useFilterStore from "@/stores/listFilterStore";
-import {withIronSessionSsr} from "iron-session/next";
+import { withIronSessionSsr } from "iron-session/next";
import Head from "next/head";
-import {useRouter} from "next/router";
-import {useEffect} from "react";
-import {BsArrowLeft} from "react-icons/bs";
-import {ToastContainer} from "react-toastify";
+import { useRouter } from "next/router";
+import { useEffect } from "react";
+import { BsArrowLeft } from "react-icons/bs";
+import { ToastContainer } from "react-toastify";
import UserList from "../(admin)/Lists/UserList";
-export const getServerSideProps = withIronSessionSsr(({req, res}) => {
- const user = req.session.user;
+export const getServerSideProps = withIronSessionSsr(({ req, res }) => {
+ const user = req.session.user;
- const envVariables: {[key: string]: string} = {};
- Object.keys(process.env)
- .filter((x) => x.startsWith("NEXT_PUBLIC"))
- .forEach((x: string) => {
- envVariables[x] = process.env[x]!;
- });
+ const envVariables: { [key: string]: string } = {};
+ Object.keys(process.env)
+ .filter((x) => x.startsWith("NEXT_PUBLIC"))
+ .forEach((x: string) => {
+ envVariables[x] = process.env[x]!;
+ });
- if (!user || !user.isVerified) {
- return {
- redirect: {
- destination: "/login",
- permanent: false,
- }
- };
- }
+ if (!user || !user.isVerified) {
+ return {
+ redirect: {
+ destination: "/login",
+ permanent: false,
+ },
+ };
+ }
- return {
- props: {user: req.session.user, envVariables},
- };
+ return {
+ props: { user: req.session.user, envVariables },
+ };
}, sessionOptions);
export default function UsersListPage() {
- const {user} = useUser();
- const {users} = useUsers();
- const [filters, clearFilters] = useFilterStore((state) => [state.userFilters, state.clearUserFilters]);
- const router = useRouter();
+ const { user } = useUser();
+ const { users } = useUsers();
+ const [filters, clearFilters] = useFilterStore((state) => [
+ state.userFilters,
+ state.clearUserFilters,
+ ]);
+ const router = useRouter();
- return (
- <>
-
- EnCoach
-
-
-
-
-
+ return (
+ <>
+
+ EnCoach
+
+
+
+
+
- {user && (
-
-
-
{
- clearFilters();
- router.back();
- }}
- className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
-
- Back
-
-
Users ({filters.map((f) => f.filter).reduce((d, f) => d.filter(f), users).length})
-
-
- f.filter)} />
-
- )}
- >
- );
+ {user && (
+
+ f.filter)}
+ renderHeader={(total) => (
+
+
{
+ clearFilters();
+ router.back();
+ }}
+ className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"
+ >
+
+ Back
+
+
Users ({total})
+
+ )}
+ />
+
+ )}
+ >
+ );
}
diff --git a/src/pages/record.tsx b/src/pages/record.tsx
index df14aa3d..fb468dfb 100644
--- a/src/pages/record.tsx
+++ b/src/pages/record.tsx
@@ -1,415 +1,614 @@
/* eslint-disable @next/next/no-img-element */
import Head from "next/head";
-import {withIronSessionSsr} from "iron-session/next";
-import {sessionOptions} from "@/lib/session";
-import {Stat, User} from "@/interfaces/user";
-import {useEffect, useState} from "react";
+import { withIronSessionSsr } from "iron-session/next";
+import { sessionOptions } from "@/lib/session";
+import { Stat, User } from "@/interfaces/user";
+import { useEffect, useState } from "react";
import useStats from "@/hooks/useStats";
-import {convertToUserSolutions, groupByDate} from "@/utils/stats";
+import { convertToUserSolutions, groupByDate } from "@/utils/stats";
import moment from "moment";
import useUsers from "@/hooks/useUsers";
import useExamStore from "@/stores/examStore";
-import {Module} from "@/interfaces";
-import {ToastContainer} from "react-toastify";
-import {useRouter} from "next/router";
-import {uniqBy} from "lodash";
-import {getExamById} from "@/utils/exams";
-import {sortByModule} from "@/utils/moduleUtils";
+import { Module } from "@/interfaces";
+import { ToastContainer } from "react-toastify";
+import { useRouter } from "next/router";
+import { uniqBy } from "lodash";
+import { getExamById } from "@/utils/exams";
+import { sortByModule } from "@/utils/moduleUtils";
import Layout from "@/components/High/Layout";
import clsx from "clsx";
-import {calculateBandScore} from "@/utils/score";
-import {BsBook, BsClipboard, BsClock, BsHeadphones, BsMegaphone, BsPen, BsPersonDash, BsPersonFillX, BsXCircle} from "react-icons/bs";
+import { calculateBandScore } from "@/utils/score";
+import {
+ BsBook,
+ BsClipboard,
+ BsClock,
+ BsHeadphones,
+ BsMegaphone,
+ BsPen,
+ BsPersonDash,
+ BsPersonFillX,
+ BsXCircle,
+} from "react-icons/bs";
import Select from "@/components/Low/Select";
import useGroups from "@/hooks/useGroups";
-import {shouldRedirectHome} from "@/utils/navigation.disabled";
+import { shouldRedirectHome } from "@/utils/navigation.disabled";
import useAssignments from "@/hooks/useAssignments";
-import {uuidv4} from "@firebase/util";
-import {usePDFDownload} from "@/hooks/usePDFDownload";
+import { uuidv4 } from "@firebase/util";
+import { usePDFDownload } from "@/hooks/usePDFDownload";
+import useRecordStore from "@/stores/recordStore";
+export const getServerSideProps = withIronSessionSsr(({ req, res }) => {
+ const user = req.session.user;
-export const getServerSideProps = withIronSessionSsr(({req, res}) => {
- const user = req.session.user;
+ if (!user || !user.isVerified) {
+ return {
+ redirect: {
+ destination: "/login",
+ permanent: false,
+ },
+ };
+ }
- if (!user || !user.isVerified) {
- return {
- redirect: {
- destination: "/login",
- permanent: false,
- },
- };
- }
+ if (shouldRedirectHome(user)) {
+ return {
+ redirect: {
+ destination: "/",
+ permanent: false,
+ },
+ };
+ }
- if (shouldRedirectHome(user)) {
- return {
- redirect: {
- destination: "/",
- permanent: false,
- },
- };
- }
-
- return {
- props: {user: req.session.user},
- };
+ return {
+ props: { user: req.session.user },
+ };
}, sessionOptions);
-export default function History({user}: {user: User}) {
- const [statsUserId, setStatsUserId] = useState(user.id);
- const [groupedStats, setGroupedStats] = useState<{[key: string]: Stat[]}>();
- const [filter, setFilter] = useState<"months" | "weeks" | "days" | "assignments">();
- const {assignments} = useAssignments({});
+const defaultSelectableCorporate = {
+ value: "",
+ label: "All",
+};
- const {users} = useUsers();
- const {stats, isLoading: isStatsLoading} = useStats(statsUserId);
- const {groups} = useGroups(user.id);
+export default function History({ user }: { user: User }) {
+ const [statsUserId, setStatsUserId] = useRecordStore((state) => [
+ state.selectedUser,
+ state.setSelectedUser,
+ ]);
+ // const [statsUserId, setStatsUserId] = useState(user.id);
+ const [groupedStats, setGroupedStats] = useState<{ [key: string]: Stat[] }>();
+ const [filter, setFilter] = useState<
+ "months" | "weeks" | "days" | "assignments"
+ >();
+ const { assignments } = useAssignments({});
- const setExams = useExamStore((state) => state.setExams);
- const setShowSolutions = useExamStore((state) => state.setShowSolutions);
- const setUserSolutions = useExamStore((state) => state.setUserSolutions);
- const setSelectedModules = useExamStore((state) => state.setSelectedModules);
- const setInactivity = useExamStore((state) => state.setInactivity);
- const setTimeSpent = useExamStore((state) => state.setTimeSpent);
- const router = useRouter();
- const renderPdfIcon = usePDFDownload("stats");
+ const { users } = useUsers();
+ const { stats, isLoading: isStatsLoading } = useStats(statsUserId);
+ const { groups: allGroups } = useGroups();
- useEffect(() => {
- if (stats && !isStatsLoading) {
- setGroupedStats(
- groupByDate(
- stats.filter((x) => {
- if (
- (x.module === "writing" || x.module === "speaking") &&
- !x.isDisabled &&
- !x.solutions.every((y) => Object.keys(y).includes("evaluation"))
- )
- return false;
- return true;
- }),
- ),
- );
- }
- }, [stats, isStatsLoading]);
+ const groups = allGroups.filter((x) => x.admin === user.id);
- const toggleFilter = (value: "months" | "weeks" | "days" | "assignments") => {
- setFilter((prev) => (prev === value ? undefined : value));
- };
+ const setExams = useExamStore((state) => state.setExams);
+ const setShowSolutions = useExamStore((state) => state.setShowSolutions);
+ const setUserSolutions = useExamStore((state) => state.setUserSolutions);
+ const setSelectedModules = useExamStore((state) => state.setSelectedModules);
+ const setInactivity = useExamStore((state) => state.setInactivity);
+ const setTimeSpent = useExamStore((state) => state.setTimeSpent);
+ const router = useRouter();
+ const renderPdfIcon = usePDFDownload("stats");
- const filterStatsByDate = (stats: {[key: string]: Stat[]}) => {
- if (filter && filter !== "assignments") {
- const filterDate = moment()
- .subtract({[filter as string]: 1})
- .format("x");
- const filteredStats: {[key: string]: Stat[]} = {};
+ useEffect(() => {
+ if (stats && !isStatsLoading) {
+ setGroupedStats(
+ groupByDate(
+ stats.filter((x) => {
+ if (
+ (x.module === "writing" || x.module === "speaking") &&
+ !x.isDisabled &&
+ !x.solutions.every((y) => Object.keys(y).includes("evaluation"))
+ )
+ return false;
+ return true;
+ })
+ )
+ );
+ }
+ }, [stats, isStatsLoading]);
- Object.keys(stats).forEach((timestamp) => {
- if (timestamp >= filterDate) filteredStats[timestamp] = stats[timestamp];
- });
+ // useEffect(() => {
+ // // just set this initially
+ // if (!statsUserId) setStatsUserId(user.id);
+ // }, []);
- return filteredStats;
- }
+ const toggleFilter = (value: "months" | "weeks" | "days" | "assignments") => {
+ setFilter((prev) => (prev === value ? undefined : value));
+ };
- if (filter && filter === "assignments") {
- const filteredStats: {[key: string]: Stat[]} = {};
+ const filterStatsByDate = (stats: { [key: string]: Stat[] }) => {
+ if (filter && filter !== "assignments") {
+ const filterDate = moment()
+ .subtract({ [filter as string]: 1 })
+ .format("x");
+ const filteredStats: { [key: string]: Stat[] } = {};
- Object.keys(stats).forEach((timestamp) => {
- if (stats[timestamp].map((s) => s.assignment === undefined).includes(false))
- filteredStats[timestamp] = [...stats[timestamp].filter((s) => !!s.assignment)];
- });
+ Object.keys(stats).forEach((timestamp) => {
+ if (timestamp >= filterDate)
+ filteredStats[timestamp] = stats[timestamp];
+ });
- return filteredStats;
- }
+ return filteredStats;
+ }
- return stats;
- };
+ if (filter && filter === "assignments") {
+ const filteredStats: { [key: string]: Stat[] } = {};
- const formatTimestamp = (timestamp: string) => {
- const date = moment(parseInt(timestamp));
- const formatter = "YYYY/MM/DD - HH:mm";
+ Object.keys(stats).forEach((timestamp) => {
+ if (
+ stats[timestamp]
+ .map((s) => s.assignment === undefined)
+ .includes(false)
+ )
+ filteredStats[timestamp] = [
+ ...stats[timestamp].filter((s) => !!s.assignment),
+ ];
+ });
- return date.format(formatter);
- };
+ return filteredStats;
+ }
- const aggregateScoresByModule = (stats: Stat[]): {module: Module; total: number; missing: number; correct: number}[] => {
- const scores: {[key in Module]: {total: number; missing: number; correct: number}} = {
- reading: {
- total: 0,
- correct: 0,
- missing: 0,
- },
- listening: {
- total: 0,
- correct: 0,
- missing: 0,
- },
- writing: {
- total: 0,
- correct: 0,
- missing: 0,
- },
- speaking: {
- total: 0,
- correct: 0,
- missing: 0,
- },
- level: {
- total: 0,
- correct: 0,
- missing: 0,
- },
- };
+ return stats;
+ };
- stats.forEach((x) => {
- scores[x.module!] = {
- total: scores[x.module!].total + x.score.total,
- correct: scores[x.module!].correct + x.score.correct,
- missing: scores[x.module!].missing + x.score.missing,
- };
- });
+ const formatTimestamp = (timestamp: string) => {
+ const date = moment(parseInt(timestamp));
+ const formatter = "YYYY/MM/DD - HH:mm";
- return Object.keys(scores)
- .filter((x) => scores[x as Module].total > 0)
- .map((x) => ({module: x as Module, ...scores[x as Module]}));
- };
+ return date.format(formatter);
+ };
- const customContent = (timestamp: string) => {
- if (!groupedStats) return <>>;
+ const aggregateScoresByModule = (
+ stats: Stat[]
+ ): { module: Module; total: number; missing: number; correct: number }[] => {
+ const scores: {
+ [key in Module]: { total: number; missing: number; correct: number };
+ } = {
+ reading: {
+ total: 0,
+ correct: 0,
+ missing: 0,
+ },
+ listening: {
+ total: 0,
+ correct: 0,
+ missing: 0,
+ },
+ writing: {
+ total: 0,
+ correct: 0,
+ missing: 0,
+ },
+ speaking: {
+ total: 0,
+ correct: 0,
+ missing: 0,
+ },
+ level: {
+ total: 0,
+ correct: 0,
+ missing: 0,
+ },
+ };
- const dateStats = groupedStats[timestamp];
- const correct = dateStats.reduce((accumulator, current) => accumulator + current.score.correct, 0);
- const total = dateStats.reduce((accumulator, current) => accumulator + current.score.total, 0);
- const aggregatedScores = aggregateScoresByModule(dateStats).filter((x) => x.total > 0);
- const assignmentID = dateStats.reduce((_, current) => current.assignment as any, "");
- const assignment = assignments.find((a) => a.id === assignmentID);
- const isDisabled = dateStats.some((x) => x.isDisabled);
+ stats.forEach((x) => {
+ scores[x.module!] = {
+ total: scores[x.module!].total + x.score.total,
+ correct: scores[x.module!].correct + x.score.correct,
+ missing: scores[x.module!].missing + x.score.missing,
+ };
+ });
- const aggregatedLevels = aggregatedScores.map((x) => ({
- module: x.module,
- level: calculateBandScore(x.correct, x.total, x.module, user.focus),
- }));
+ return Object.keys(scores)
+ .filter((x) => scores[x as Module].total > 0)
+ .map((x) => ({ module: x as Module, ...scores[x as Module] }));
+ };
- const {timeSpent, inactivity, session} = dateStats[0];
+ const customContent = (timestamp: string) => {
+ if (!groupedStats) return <>>;
- const selectExam = () => {
- const examPromises = uniqBy(dateStats, "exam").map((stat) => {
- console.log({stat});
- return getExamById(stat.module, stat.exam);
- });
+ const dateStats = groupedStats[timestamp];
+ const correct = dateStats.reduce(
+ (accumulator, current) => accumulator + current.score.correct,
+ 0
+ );
+ const total = dateStats.reduce(
+ (accumulator, current) => accumulator + current.score.total,
+ 0
+ );
+ const aggregatedScores = aggregateScoresByModule(dateStats).filter(
+ (x) => x.total > 0
+ );
+ const assignmentID = dateStats.reduce(
+ (_, current) => current.assignment as any,
+ ""
+ );
+ const assignment = assignments.find((a) => a.id === assignmentID);
+ const isDisabled = dateStats.some((x) => x.isDisabled);
- Promise.all(examPromises).then((exams) => {
- if (exams.every((x) => !!x)) {
- if (!!timeSpent) setTimeSpent(timeSpent);
- if (!!inactivity) setInactivity(inactivity);
+ const aggregatedLevels = aggregatedScores.map((x) => ({
+ module: x.module,
+ level: calculateBandScore(x.correct, x.total, x.module, user.focus),
+ }));
- setUserSolutions(convertToUserSolutions(dateStats));
- setShowSolutions(true);
- setExams(exams.map((x) => x!).sort(sortByModule));
- setSelectedModules(
- exams
- .map((x) => x!)
- .sort(sortByModule)
- .map((x) => x!.module),
- );
- router.push("/exercises");
- }
- });
- };
+ const { timeSpent, inactivity, session } = dateStats[0];
- const textColor = clsx(
- correct / total >= 0.7 && "text-mti-purple",
- correct / total >= 0.3 && correct / total < 0.7 && "text-mti-red",
- correct / total < 0.3 && "text-mti-rose",
- );
+ const selectExam = () => {
+ const examPromises = uniqBy(dateStats, "exam").map((stat) => {
+ console.log({ stat });
+ return getExamById(stat.module, stat.exam);
+ });
- const content = (
- <>
-
-
-
{formatTimestamp(timestamp)}
-
- {!!timeSpent && (
-
- {Math.floor(timeSpent / 60)} minutes
-
- )}
- {!!inactivity && (
-
- {Math.floor(inactivity / 60)} minutes
-
- )}
-
-
-
-
- Level{" "}
- {(aggregatedLevels.reduce((accumulator, current) => accumulator + current.level, 0) / aggregatedLevels.length).toFixed(1)}
-
- {renderPdfIcon(session, textColor, textColor)}
-
-
+ Promise.all(examPromises).then((exams) => {
+ if (exams.every((x) => !!x)) {
+ if (!!timeSpent) setTimeSpent(timeSpent);
+ if (!!inactivity) setInactivity(inactivity);
-
-
- {aggregatedLevels.map(({module, level}) => (
-
- {module === "reading" && }
- {module === "listening" && }
- {module === "writing" && }
- {module === "speaking" && }
- {module === "level" && }
- {level.toFixed(1)}
-
- ))}
-
+ setUserSolutions(convertToUserSolutions(dateStats));
+ setShowSolutions(true);
+ setExams(exams.map((x) => x!).sort(sortByModule));
+ setSelectedModules(
+ exams
+ .map((x) => x!)
+ .sort(sortByModule)
+ .map((x) => x!.module)
+ );
+ router.push("/exercises");
+ }
+ });
+ };
- {assignment && (
-
- Assignment: {assignment.name}, Teacher: {users.find((u) => u.id === assignment.assigner)?.name}
-
- )}
-
- >
- );
+ const textColor = clsx(
+ correct / total >= 0.7 && "text-mti-purple",
+ correct / total >= 0.3 && correct / total < 0.7 && "text-mti-red",
+ correct / total < 0.3 && "text-mti-rose"
+ );
- return (
- <>
- = 0.7 && "hover:border-mti-purple",
- correct / total >= 0.3 && correct / total < 0.7 && "hover:border-mti-red",
- correct / total < 0.3 && "hover:border-mti-rose",
- )}
- onClick={isDisabled ? () => null : selectExam}
- data-tip="This exam is still being evaluated..."
- role="button">
- {content}
-
- = 0.7 && "hover:border-mti-purple",
- correct / total >= 0.3 && correct / total < 0.7 && "hover:border-mti-red",
- correct / total < 0.3 && "hover:border-mti-rose",
- )}
- data-tip="Your screen size is too small to view previous exams."
- role="button">
- {content}
-
- >
- );
- };
+ const content = (
+ <>
+
+
+
{formatTimestamp(timestamp)}
+
+ {!!timeSpent && (
+
+ {Math.floor(timeSpent / 60)} minutes
+
+ )}
+ {!!inactivity && (
+
+ {Math.floor(inactivity / 60)} minutes
+
+ )}
+
+
+
+
+ Level{" "}
+ {(
+ aggregatedLevels.reduce(
+ (accumulator, current) => accumulator + current.level,
+ 0
+ ) / aggregatedLevels.length
+ ).toFixed(1)}
+
+ {renderPdfIcon(session, textColor, textColor)}
+
+
- return (
- <>
-
- Record | EnCoach
-
-
-
-
-
- {user && (
-
-
-
- {(user.type === "developer" || user.type === "admin") && (
-
-
-
-
-
-
-
-
- {groupedStats && Object.keys(groupedStats).length > 0 && !isStatsLoading && (
-
- {Object.keys(filterStatsByDate(groupedStats))
- .sort((a, b) => parseInt(b) - parseInt(a))
- .map(customContent)}
-
- )}
- {groupedStats && Object.keys(groupedStats).length === 0 && !isStatsLoading && (
- No record to display...
- )}
-
- )}
- >
- );
+
+
+ {aggregatedLevels.map(({ module, level }) => (
+
+ {module === "reading" && }
+ {module === "listening" && }
+ {module === "writing" && }
+ {module === "speaking" && }
+ {module === "level" && }
+ {level.toFixed(1)}
+
+ ))}
+
+
+ {assignment && (
+
+ Assignment: {assignment.name}, Teacher:{" "}
+ {users.find((u) => u.id === assignment.assigner)?.name}
+
+ )}
+
+ >
+ );
+
+ return (
+ <>
+ = 0.7 && "hover:border-mti-purple",
+ correct / total >= 0.3 &&
+ correct / total < 0.7 &&
+ "hover:border-mti-red",
+ correct / total < 0.3 && "hover:border-mti-rose"
+ )}
+ onClick={isDisabled ? () => null : selectExam}
+ data-tip="This exam is still being evaluated..."
+ role="button"
+ >
+ {content}
+
+ = 0.7 && "hover:border-mti-purple",
+ correct / total >= 0.3 &&
+ correct / total < 0.7 &&
+ "hover:border-mti-red",
+ correct / total < 0.3 && "hover:border-mti-rose"
+ )}
+ data-tip="Your screen size is too small to view previous exams."
+ role="button"
+ >
+ {content}
+
+ >
+ );
+ };
+
+ const selectableCorporates = [
+ defaultSelectableCorporate,
+ ...users
+ .filter((x) => x.type === "corporate")
+ .map((x) => ({
+ value: x.id,
+ label: `${x.name} - ${x.email}`,
+ })),
+ ];
+
+ const [selectedCorporate, setSelectedCorporate] = useState(
+ defaultSelectableCorporate.value
+ );
+
+ const getUsersList = (): User[] => {
+ if (selectedCorporate) {
+ // get groups for that corporate
+ const selectedCorporateGroups = allGroups.filter(
+ (x) => x.admin === selectedCorporate
+ );
+
+ // get the teacher ids for that group
+ const selectedCorporateGroupsParticipants =
+ selectedCorporateGroups.flatMap((x) => x.participants);
+
+ // // search for groups for these teachers
+ // const teacherGroups = allGroups.filter((x) => {
+ // return selectedCorporateGroupsParticipants.includes(x.admin);
+ // });
+
+ // const usersList = [
+ // ...selectedCorporateGroupsParticipants,
+ // ...teacherGroups.flatMap((x) => x.participants),
+ // ];
+ const userListWithUsers = selectedCorporateGroupsParticipants.map((x) =>
+ users.find((y) => y.id === x)
+ ) as User[];
+ return userListWithUsers.filter((x) => x);
+ }
+
+ return users || [];
+ };
+
+ const corporateFilteredUserList = getUsersList();
+
+ const getSelectedUser = () => {
+ if (selectedCorporate) {
+ const userInCorporate = corporateFilteredUserList.find(
+ (x) => x.id === statsUserId
+ );
+ return userInCorporate || corporateFilteredUserList[0];
+ }
+
+ return users.find((x) => x.id === statsUserId) || user;
+ };
+
+ const selectedUser = getSelectedUser();
+ const selectedUserSelectValue = selectedUser
+ ? {
+ value: selectedUser.id,
+ label: `${selectedUser.name} - ${selectedUser.email}`,
+ }
+ : {
+ value: "",
+ label: "",
+ };
+ return (
+ <>
+
+ Record | EnCoach
+
+
+
+
+
+ {user && (
+
+
+
+ {(user.type === "developer" || user.type === "admin") && (
+ <>
+
+
+ x.value === selectedCorporate
+ )}
+ onChange={(value) =>
+ setSelectedCorporate(value?.value || "")
+ }
+ styles={{
+ menuPortal: (base) => ({ ...base, zIndex: 9999 }),
+ option: (styles, state) => ({
+ ...styles,
+ backgroundColor: state.isFocused
+ ? "#D5D9F0"
+ : state.isSelected
+ ? "#7872BF"
+ : "white",
+ color: state.isFocused ? "black" : styles.color,
+ }),
+ }}
+ >
+
+
+ ({
+ value: x.id,
+ label: `${x.name} - ${x.email}`,
+ }))}
+ value={selectedUserSelectValue}
+ onChange={(value) => setStatsUserId(value?.value)}
+ styles={{
+ menuPortal: (base) => ({ ...base, zIndex: 9999 }),
+ option: (styles, state) => ({
+ ...styles,
+ backgroundColor: state.isFocused
+ ? "#D5D9F0"
+ : state.isSelected
+ ? "#7872BF"
+ : "white",
+ color: state.isFocused ? "black" : styles.color,
+ }),
+ }}
+ />
+ >
+ )}
+ {(user.type === "corporate" || user.type === "teacher") &&
+ groups.length > 0 && (
+ <>
+
+
+
+ groups.flatMap((y) => y.participants).includes(x.id)
+ )
+ .map((x) => ({
+ value: x.id,
+ label: `${x.name} - ${x.email}`,
+ }))}
+ value={selectedUserSelectValue}
+ onChange={(value) => setStatsUserId(value?.value)}
+ styles={{
+ menuPortal: (base) => ({ ...base, zIndex: 9999 }),
+ option: (styles, state) => ({
+ ...styles,
+ backgroundColor: state.isFocused
+ ? "#D5D9F0"
+ : state.isSelected
+ ? "#7872BF"
+ : "white",
+ color: state.isFocused ? "black" : styles.color,
+ }),
+ }}
+ />
+ >
+ )}
+
+
+
+
+
+
+
+
+ {groupedStats &&
+ Object.keys(groupedStats).length > 0 &&
+ !isStatsLoading && (
+
+ {Object.keys(filterStatsByDate(groupedStats))
+ .sort((a, b) => parseInt(b) - parseInt(a))
+ .map(customContent)}
+
+ )}
+ {groupedStats &&
+ Object.keys(groupedStats).length === 0 &&
+ !isStatsLoading && (
+
+ No record to display...
+
+ )}
+
+ )}
+ >
+ );
}
diff --git a/src/pages/tickets.tsx b/src/pages/tickets.tsx
index 301dfab5..ca06ff96 100644
--- a/src/pages/tickets.tsx
+++ b/src/pages/tickets.tsx
@@ -68,16 +68,40 @@ const SOURCE_OPTIONS = [
{value: "platform", label: "Platform"},
];
+type CustomStatus = TicketStatus | "all" | "pending";
+
+const STATUS_OPTIONS = [{
+ label: 'Pending',
+ value: 'pending',
+ filter: (x: Ticket) => x.status !== 'completed',
+}, {
+ label: 'All',
+ value: 'all',
+ filter: (x: Ticket) => true,
+}, {
+ label: 'Completed',
+ value: 'completed',
+ filter: (x: Ticket) => x.status === 'completed',
+}, {
+ label: 'In Progress',
+ value: 'in-progress',
+ filter: (x: Ticket) => x.status === 'in-progress',
+}, {
+ label: 'Submitted',
+ value: 'submitted',
+ filter: (x: Ticket) => x.status === 'submitted',
+}]
+
export default function Tickets() {
const [filteredTickets, setFilteredTickets] = useState([]);
const [selectedTicket, setSelectedTicket] = useState();
const [assigneeFilter, setAssigneeFilter] = useState();
const [sourceFilter, setSourceFilter] = useState("");
-
+ // const [statusFilter, setStatusFilter] = useState('pending');
const [dateSorting, setDateSorting] = useState<"asc" | "desc">("desc");
const [typeFilter, setTypeFilter] = useState();
- const [statusFilter, setStatusFilter] = useState();
+ const [statusFilter, setStatusFilter] = useState('pending');
const {user} = useUser({redirectTo: "/login"});
const {users} = useUsers();
@@ -91,7 +115,10 @@ export default function Tickets() {
const filters = [];
if (user?.type === "agent") filters.push((x: Ticket) => x.assignedTo === user.id);
if (typeFilter) filters.push((x: Ticket) => x.type === typeFilter);
- if (statusFilter) filters.push((x: Ticket) => x.status === statusFilter);
+ if (statusFilter) {
+ const filter = STATUS_OPTIONS.find(x => x.value === statusFilter)?.filter;
+ if (filter) filters.push(filter);
+ }
if (assigneeFilter) filters.push((x: Ticket) => x.assignedTo === assigneeFilter);
if (sourceFilter) {
if (sourceFilter === "webpage") filters.push((x: Ticket) => fromHomepage.some((r) => r.test(x.reportedFrom)));
@@ -214,17 +241,9 @@ export default function Tickets() {
({
- value: x,
- label: TicketStatusLabel[x as keyof typeof TicketStatusLabel],
- }))}
+ options={STATUS_OPTIONS}
value={
- statusFilter
- ? {
- value: statusFilter,
- label: TicketStatusLabel[statusFilter],
- }
- : undefined
+ STATUS_OPTIONS.find((x) => x.value === statusFilter)
}
onChange={(value) => setStatusFilter((value?.value as TicketStatus) ?? undefined)}
isClearable
diff --git a/src/stores/recordStore.ts b/src/stores/recordStore.ts
new file mode 100644
index 00000000..ecd743e3
--- /dev/null
+++ b/src/stores/recordStore.ts
@@ -0,0 +1,18 @@
+
+import {create} from "zustand";
+
+export interface RecordState {
+ selectedUser?: string;
+ setSelectedUser: (selectedUser: string | undefined) => void;
+}
+
+export const initialState = {
+ selectedUser: undefined,
+};
+
+const recordStore = create((set) => ({
+ ...initialState,
+ setSelectedUser: (selectedUser: string | undefined) => set(() => ({selectedUser})),
+}));
+
+export default recordStore;