Merged develop into feature/ExamGenRework

This commit is contained in:
carlos.mesquita
2024-12-11 15:34:09 +00:00
53 changed files with 2084 additions and 6520 deletions

View File

@@ -28,7 +28,7 @@ const CreatorCell = ({ id, users }: { id: string; users: User[] }) => {
return (
<>
{(creatorUser?.type === "corporate" ? creatorUser?.corporateInformation?.companyInformation?.name : creatorUser?.name || "N/A") || "N/A"}{" "}
{(creatorUser?.name || "N/A") || "N/A"}{" "}
{creatorUser && `(${USER_TYPE_LABELS[creatorUser?.type]})`}
</>
);
@@ -213,10 +213,7 @@ export default function CodeList({ user, canDeleteCodes }: { user: User, canDele
value={
filteredCorporate
? {
label: `${filteredCorporate?.type === "corporate"
? filteredCorporate.corporateInformation?.companyInformation?.name || filteredCorporate.name
: filteredCorporate.name
} (${USER_TYPE_LABELS[filteredCorporate?.type]})`,
label: `${filteredCorporate.name} (${USER_TYPE_LABELS[filteredCorporate?.type]})`,
value: filteredCorporate.id,
}
: null
@@ -224,8 +221,7 @@ export default function CodeList({ user, canDeleteCodes }: { user: User, canDele
options={users
.filter((x) => ["admin", "developer", "corporate"].includes(x.type))
.map((x) => ({
label: `${x.type === "corporate" ? x.corporateInformation?.companyInformation?.name || x.name : x.name} (${USER_TYPE_LABELS[x.type]
})`,
label: `${x.name} (${USER_TYPE_LABELS[x.type]})`,
value: x.id,
user: x,
}))}

View File

@@ -29,419 +29,417 @@ const columnHelper = createColumnHelper<WithLabeledEntities<User>>();
const searchFields = [["name"], ["email"], ["entities", ""]];
export default function UserList({
user,
filters = [],
type,
renderHeader,
user,
filters = [],
type,
renderHeader,
}: {
user: User;
filters?: ((user: User) => boolean)[];
type?: Type;
renderHeader?: (total: number) => JSX.Element;
user: User;
filters?: ((user: User) => boolean)[];
type?: Type;
renderHeader?: (total: number) => JSX.Element;
}) {
const [showDemographicInformation, setShowDemographicInformation] = useState(false);
const [selectedUser, setSelectedUser] = useState<User>();
const [showDemographicInformation, setShowDemographicInformation] = useState(false);
const [selectedUser, setSelectedUser] = useState<User>();
const { users, reload } = useEntitiesUsers(type)
const { entities } = useEntities()
const { users, reload } = useEntitiesUsers(type)
const { entities } = useEntities()
const { balance } = useUserBalance();
const { balance } = useUserBalance();
const isAdmin = useMemo(() => ["admin", "developer"].includes(user?.type), [user?.type])
const isAdmin = useMemo(() => ["admin", "developer"].includes(user?.type), [user?.type])
const entitiesViewStudents = useAllowedEntities(user, entities, "view_students")
const entitiesEditStudents = useAllowedEntities(user, entities, "edit_students")
const entitiesDeleteStudents = useAllowedEntities(user, entities, "delete_students")
const entitiesViewStudents = useAllowedEntities(user, entities, "view_students")
const entitiesEditStudents = useAllowedEntities(user, entities, "edit_students")
const entitiesDeleteStudents = useAllowedEntities(user, entities, "delete_students")
const entitiesViewTeachers = useAllowedEntities(user, entities, "view_teachers")
const entitiesEditTeachers = useAllowedEntities(user, entities, "edit_teachers")
const entitiesDeleteTeachers = useAllowedEntities(user, entities, "delete_teachers")
const entitiesViewTeachers = useAllowedEntities(user, entities, "view_teachers")
const entitiesEditTeachers = useAllowedEntities(user, entities, "edit_teachers")
const entitiesDeleteTeachers = useAllowedEntities(user, entities, "delete_teachers")
const entitiesViewCorporates = useAllowedEntities(user, entities, "view_corporates")
const entitiesEditCorporates = useAllowedEntities(user, entities, "edit_corporates")
const entitiesDeleteCorporates = useAllowedEntities(user, entities, "delete_corporates")
const entitiesViewCorporates = useAllowedEntities(user, entities, "view_corporates")
const entitiesEditCorporates = useAllowedEntities(user, entities, "edit_corporates")
const entitiesDeleteCorporates = useAllowedEntities(user, entities, "delete_corporates")
const entitiesViewMasterCorporates = useAllowedEntities(user, entities, "view_mastercorporates")
const entitiesEditMasterCorporates = useAllowedEntities(user, entities, "edit_mastercorporates")
const entitiesDeleteMasterCorporates = useAllowedEntities(user, entities, "delete_mastercorporates")
const entitiesViewMasterCorporates = useAllowedEntities(user, entities, "view_mastercorporates")
const entitiesEditMasterCorporates = useAllowedEntities(user, entities, "edit_mastercorporates")
const entitiesDeleteMasterCorporates = useAllowedEntities(user, entities, "delete_mastercorporates")
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";
};
const allowedUsers = useMemo(() => users.filter((u) => {
const allowedUsers = useMemo(() => users.filter((u) => {
if (isAdmin) return true
if (u.id === user?.id) return false
switch (u.type) {
case "student": return mapBy((u.entities || []), 'id').some((id) => mapBy(entitiesViewStudents, 'id').includes(id))
case "teacher": return mapBy((u.entities || []), 'id').some((id) => mapBy(entitiesViewTeachers, 'id').includes(id))
case 'corporate': return mapBy((u.entities || []), 'id').some((id) => mapBy(entitiesViewCorporates, 'id').includes(id))
case 'mastercorporate': return mapBy((u.entities || []), 'id').some((id) => mapBy(entitiesViewMasterCorporates, 'id').includes(id))
default: return false
}
})
, [entitiesViewCorporates, entitiesViewMasterCorporates, entitiesViewStudents, entitiesViewTeachers, isAdmin, user?.id, users])
switch (u.type) {
case "student": return mapBy((u.entities || []), 'id').some((id) => mapBy(entitiesViewStudents, 'id').includes(id))
case "teacher": return mapBy((u.entities || []), 'id').some((id) => mapBy(entitiesViewTeachers, 'id').includes(id))
case 'corporate': return mapBy((u.entities || []), 'id').some((id) => mapBy(entitiesViewCorporates, 'id').includes(id))
case 'mastercorporate': return mapBy((u.entities || []), 'id').some((id) => mapBy(entitiesViewMasterCorporates, 'id').includes(id))
default: return false
}
})
, [entitiesViewCorporates, entitiesViewMasterCorporates, entitiesViewStudents, entitiesViewTeachers, isAdmin, user?.id, users])
const displayUsers = useMemo(() =>
filters.length > 0 ? filters.reduce((d, f) => d.filter(f), allowedUsers) : allowedUsers,
[filters, allowedUsers])
const displayUsers = useMemo(() =>
filters.length > 0 ? filters.reduce((d, f) => d.filter(f), allowedUsers) : allowedUsers,
[filters, allowedUsers])
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 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 getEditPermission = (type: Type) => {
if (type === "student") return entitiesEditStudents
if (type === "teacher") return entitiesEditTeachers
if (type === "corporate") return entitiesEditCorporates
if (type === "mastercorporate") return entitiesEditMasterCorporates
const getEditPermission = (type: Type) => {
if (type === "student") return entitiesEditStudents
if (type === "teacher") return entitiesEditTeachers
if (type === "corporate") return entitiesEditCorporates
if (type === "mastercorporate") return entitiesEditMasterCorporates
return []
}
return []
}
const getDeletePermission = (type: Type) => {
if (type === "student") return entitiesDeleteStudents
if (type === "teacher") return entitiesDeleteTeachers
if (type === "corporate") return entitiesDeleteCorporates
if (type === "mastercorporate") return entitiesDeleteMasterCorporates
const getDeletePermission = (type: Type) => {
if (type === "student") return entitiesDeleteStudents
if (type === "teacher") return entitiesDeleteTeachers
if (type === "corporate") return entitiesDeleteCorporates
if (type === "mastercorporate") return entitiesDeleteMasterCorporates
return []
}
return []
}
const canEditUser = (u: User) =>
isAdmin || u.entities.some(e => mapBy(getEditPermission(u.type), 'id').includes(e.id))
const canEditUser = (u: User) =>
isAdmin || u.entities.some(e => mapBy(getEditPermission(u.type), 'id').includes(e.id))
const canDeleteUser = (u: User) =>
isAdmin || u.entities.some(e => mapBy(getDeletePermission(u.type), 'id').includes(e.id))
const canDeleteUser = (u: User) =>
isAdmin || u.entities.some(e => mapBy(getDeletePermission(u.type), 'id').includes(e.id))
const actionColumn = ({ row }: { row: { original: User } }) => {
const actionColumn = ({ row }: { row: { original: User } }) => {
const canEdit = canEditUser(row.original)
const canDelete = canDeleteUser(row.original)
return (
<div className="flex gap-4">
{!row.original.isVerified && canEdit && (
<div data-tip="Verify User" className="cursor-pointer tooltip" onClick={() => verifyAccount(row.original)}>
<BsCheck className="hover:text-mti-purple-light transition ease-in-out duration-300" />
</div>
)}
{canEdit && (
<div
data-tip={row.original.status === "disabled" ? "Enable User" : "Disable User"}
className="cursor-pointer tooltip"
onClick={() => toggleDisableAccount(row.original)}>
{row.original.status === "disabled" ? (
<BsCheckCircle className="hover:text-mti-purple-light transition ease-in-out duration-300" />
) : (
<BsFillExclamationOctagonFill className="hover:text-mti-purple-light transition ease-in-out duration-300" />
)}
</div>
)}
{canDelete && (
<div data-tip="Delete" className="cursor-pointer tooltip" onClick={() => deleteAccount(row.original)}>
<BsTrash className="hover:text-mti-purple-light transition ease-in-out duration-300" />
</div>
)}
</div>
);
};
return (
<div className="flex gap-4">
{!row.original.isVerified && canEdit && (
<div data-tip="Verify User" className="cursor-pointer tooltip" onClick={() => verifyAccount(row.original)}>
<BsCheck className="hover:text-mti-purple-light transition ease-in-out duration-300" />
</div>
)}
{canEdit && (
<div
data-tip={row.original.status === "disabled" ? "Enable User" : "Disable User"}
className="cursor-pointer tooltip"
onClick={() => toggleDisableAccount(row.original)}>
{row.original.status === "disabled" ? (
<BsCheckCircle className="hover:text-mti-purple-light transition ease-in-out duration-300" />
) : (
<BsFillExclamationOctagonFill className="hover:text-mti-purple-light transition ease-in-out duration-300" />
)}
</div>
)}
{canDelete && (
<div data-tip="Delete" className="cursor-pointer tooltip" onClick={() => deleteAccount(row.original)}>
<BsTrash className="hover:text-mti-purple-light transition ease-in-out duration-300" />
</div>
)}
</div>
);
};
const demographicColumns = [
columnHelper.accessor("name", {
header: "Name",
cell: ({ row, getValue }) => (
<div
className={clsx(
canEditUser(row.original) &&
"underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer",
)}
onClick={() =>
canEditUser(row.original) ? setSelectedUser(row.original) : null
}>
{getValue()}
</div>
),
}),
columnHelper.accessor("demographicInformation.country", {
header: "Country",
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})`
: "N/A",
}),
columnHelper.accessor("demographicInformation.phone", {
header: "Phone",
cell: (info) => info.getValue() || "N/A",
enableSorting: true,
}),
columnHelper.accessor(
(x) =>
x.type === "corporate" || x.type === "mastercorporate" ? x.demographicInformation?.position : x.demographicInformation?.employment,
{
id: "employment",
header: "Employment",
cell: (info) => (info.row.original.type === "corporate" ? info.getValue() : capitalize(info.getValue())) || "N/A",
enableSorting: true,
},
),
columnHelper.accessor("lastLogin", {
header: "Last Login",
cell: (info) => (!!info.getValue() ? moment(info.getValue()).format("YYYY-MM-DD HH:mm") : "N/A"),
}),
columnHelper.accessor("demographicInformation.gender", {
header: "Gender",
cell: (info) => capitalize(info.getValue()) || "N/A",
enableSorting: true,
}),
{
header: (
<span className="cursor-pointer" onClick={() => setShowDemographicInformation((prev) => !prev)}>
Switch
</span>
),
id: "actions",
cell: actionColumn,
sortable: false
},
];
const demographicColumns = [
columnHelper.accessor("name", {
header: "Name",
cell: ({ row, getValue }) => (
<div
className={clsx(
canEditUser(row.original) &&
"underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer",
)}
onClick={() =>
canEditUser(row.original) ? setSelectedUser(row.original) : null
}>
{getValue()}
</div>
),
}),
columnHelper.accessor("demographicInformation.country", {
header: "Country",
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})`
: "N/A",
}),
columnHelper.accessor("demographicInformation.phone", {
header: "Phone",
cell: (info) => info.getValue() || "N/A",
enableSorting: true,
}),
columnHelper.accessor(
(x) =>
x.type === "corporate" || x.type === "mastercorporate" ? x.demographicInformation?.position : x.demographicInformation?.employment,
{
id: "employment",
header: "Employment",
cell: (info) => (info.row.original.type === "corporate" ? info.getValue() : capitalize(info.getValue())) || "N/A",
enableSorting: true,
},
),
columnHelper.accessor("lastLogin", {
header: "Last Login",
cell: (info) => (!!info.getValue() ? moment(info.getValue()).format("YYYY-MM-DD HH:mm") : "N/A"),
}),
columnHelper.accessor("demographicInformation.gender", {
header: "Gender",
cell: (info) => capitalize(info.getValue()) || "N/A",
enableSorting: true,
}),
{
header: (
<span className="cursor-pointer" onClick={() => setShowDemographicInformation((prev) => !prev)}>
Switch
</span>
),
id: "actions",
cell: actionColumn,
sortable: false
},
];
const defaultColumns = [
columnHelper.accessor("name", {
header: "Name",
cell: ({ row, getValue }) => (
<div
className={clsx(
canEditUser(row.original) &&
"underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer",
)}
onClick={() =>
canEditUser(row.original) ? setSelectedUser(row.original) : null
}>
{getValue()}
</div>
),
}),
columnHelper.accessor("email", {
header: "E-mail",
cell: ({ row, getValue }) => (
<div
className={clsx(
canEditUser(row.original) &&
"underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer",
)}
onClick={() => (canEditUser(row.original) ? setSelectedUser(row.original) : null)}>
{getValue()}
</div>
),
}),
columnHelper.accessor("type", {
header: "Type",
cell: (info) => USER_TYPE_LABELS[info.getValue()],
}),
columnHelper.accessor("studentID", {
header: "Student ID",
cell: (info) => info.getValue() || "N/A",
}),
columnHelper.accessor("entities", {
header: "Entities",
cell: ({ getValue }) => mapBy(getValue(), 'label').join(', '),
}),
columnHelper.accessor("subscriptionExpirationDate", {
header: "Expiration",
cell: (info) => (
<span className={clsx(info.getValue() ? expirationDateColor(moment(info.getValue()).toDate()) : "")}>
{!info.getValue() ? "No expiry date" : moment(info.getValue()).format("DD/MM/YYYY")}
</span>
),
}),
columnHelper.accessor("isVerified", {
header: "Verified",
cell: (info) => (
<div className="flex gap-3 items-center text-mti-gray-dim text-sm self-center">
<div
className={clsx(
"w-6 h-6 rounded-md flex items-center justify-center border border-mti-purple-light bg-white",
"transition duration-300 ease-in-out",
info.getValue() && "!bg-mti-purple-light ",
)}>
<BsCheck color="white" className="w-full h-full" />
</div>
</div>
),
}),
{
header: (
<span className="cursor-pointer" onClick={() => setShowDemographicInformation((prev) => !prev)}>
Switch
</span>
),
id: "actions",
cell: actionColumn,
sortable: false
},
];
const defaultColumns = [
columnHelper.accessor("name", {
header: "Name",
cell: ({ row, getValue }) => (
<div
className={clsx(
canEditUser(row.original) &&
"underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer",
)}
onClick={() =>
canEditUser(row.original) ? setSelectedUser(row.original) : null
}>
{getValue()}
</div>
),
}),
columnHelper.accessor("email", {
header: "E-mail",
cell: ({ row, getValue }) => (
<div
className={clsx(
canEditUser(row.original) &&
"underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer",
)}
onClick={() => (canEditUser(row.original) ? setSelectedUser(row.original) : null)}>
{getValue()}
</div>
),
}),
columnHelper.accessor("type", {
header: "Type",
cell: (info) => USER_TYPE_LABELS[info.getValue()],
}),
columnHelper.accessor("studentID", {
header: "Student ID",
cell: (info) => info.getValue() || "N/A",
}),
columnHelper.accessor("entities", {
header: "Entities",
cell: ({ getValue }) => mapBy(getValue(), 'label').join(', '),
}),
columnHelper.accessor("subscriptionExpirationDate", {
header: "Expiration",
cell: (info) => (
<span className={clsx(info.getValue() ? expirationDateColor(moment(info.getValue()).toDate()) : "")}>
{!info.getValue() ? "No expiry date" : moment(info.getValue()).format("DD/MM/YYYY")}
</span>
),
}),
columnHelper.accessor("isVerified", {
header: "Verified",
cell: (info) => (
<div className="flex gap-3 items-center text-mti-gray-dim text-sm self-center">
<div
className={clsx(
"w-6 h-6 rounded-md flex items-center justify-center border border-mti-purple-light bg-white",
"transition duration-300 ease-in-out",
info.getValue() && "!bg-mti-purple-light ",
)}>
<BsCheck color="white" className="w-full h-full" />
</div>
</div>
),
}),
{
header: (
<span className="cursor-pointer" onClick={() => setShowDemographicInformation((prev) => !prev)}>
Switch
</span>
),
id: "actions",
cell: actionColumn,
sortable: false
},
];
const downloadExcel = (rows: WithLabeledEntities<User>[]) => {
const csv = exportListToExcel(rows);
const downloadExcel = (rows: WithLabeledEntities<User>[]) => {
const csv = exportListToExcel(rows);
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);
};
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);
};
const viewStudentFilter = (x: User) => x.type === "student";
const viewTeacherFilter = (x: User) => x.type === "teacher";
const belongsToAdminFilter = (x: User) => x.entities.some(({ id }) => mapBy(selectedUser?.entities || [], 'id').includes(id));
const viewStudentFilter = (x: User) => x.type === "student";
const viewTeacherFilter = (x: User) => x.type === "teacher";
const belongsToAdminFilter = (x: User) => x.entities.some(({ id }) => mapBy(selectedUser?.entities || [], 'id').includes(id));
const viewStudentFilterBelongsToAdmin = (x: User) => viewStudentFilter(x) && belongsToAdminFilter(x);
const viewTeacherFilterBelongsToAdmin = (x: User) => viewTeacherFilter(x) && belongsToAdminFilter(x);
const viewStudentFilterBelongsToAdmin = (x: User) => viewStudentFilter(x) && belongsToAdminFilter(x);
const viewTeacherFilterBelongsToAdmin = (x: User) => viewTeacherFilter(x) && belongsToAdminFilter(x);
const renderUserCard = (selectedUser: User) => {
const studentsFromAdmin = users.filter(viewStudentFilterBelongsToAdmin);
const teachersFromAdmin = users.filter(viewTeacherFilterBelongsToAdmin);
return (
<div className="w-full flex flex-col gap-8">
<UserCard
maxUserAmount={
user.type === "mastercorporate" ? (user.corporateInformation?.companyInformation?.userAmount || 0) - balance : undefined
}
loggedInUser={user}
onViewStudents={
(selectedUser.type === "corporate" || selectedUser.type === "teacher") && studentsFromAdmin.length > 0
? () => {
appendUserFilters({
id: "view-students",
filter: viewStudentFilter,
});
appendUserFilters({
id: "belongs-to-admin",
filter: belongsToAdminFilter,
});
const renderUserCard = (selectedUser: User) => {
const studentsFromAdmin = users.filter(viewStudentFilterBelongsToAdmin);
const teachersFromAdmin = users.filter(viewTeacherFilterBelongsToAdmin);
return (
<div className="w-full flex flex-col gap-8">
<UserCard
maxUserAmount={0}
loggedInUser={user}
onViewStudents={
(selectedUser.type === "corporate" || selectedUser.type === "teacher") && studentsFromAdmin.length > 0
? () => {
appendUserFilters({
id: "view-students",
filter: viewStudentFilter,
});
appendUserFilters({
id: "belongs-to-admin",
filter: belongsToAdminFilter,
});
router.push("/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("/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("/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: belongsToAdminFilter
});
router.push("/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: belongsToAdminFilter
});
router.push("/users");
}
: undefined
}
onClose={(shouldReload) => {
setSelectedUser(undefined);
if (shouldReload) reload();
}}
user={selectedUser}
/>
</div>
);
};
router.push("/users");
}
: undefined
}
onClose={(shouldReload) => {
setSelectedUser(undefined);
if (shouldReload) reload();
}}
user={selectedUser}
/>
</div>
);
};
return (
<>
{renderHeader && renderHeader(displayUsers.length)}
<div className="w-full">
<Modal isOpen={!!selectedUser} onClose={() => setSelectedUser(undefined)}>
{selectedUser && renderUserCard(selectedUser)}
</Modal>
<Table<WithLabeledEntities<User>>
data={displayUsers}
columns={(!showDemographicInformation ? defaultColumns : demographicColumns) as any}
searchFields={searchFields}
onDownload={downloadExcel}
/>
</div>
</>
);
return (
<>
{renderHeader && renderHeader(displayUsers.length)}
<div className="w-full">
<Modal isOpen={!!selectedUser} onClose={() => setSelectedUser(undefined)}>
{selectedUser && renderUserCard(selectedUser)}
</Modal>
<Table<WithLabeledEntities<User>>
data={displayUsers}
columns={(!showDemographicInformation ? defaultColumns : demographicColumns) as any}
searchFields={searchFields}
onDownload={downloadExcel}
/>
</div>
</>
);
}

View File

@@ -13,271 +13,267 @@ import moment from "moment";
import useAcceptedTerms from "@/hooks/useAcceptedTerms";
interface Props {
isLoading: boolean;
setIsLoading: (isLoading: boolean) => void;
mutateUser: KeyedMutator<User>;
sendEmailVerification: typeof sendEmailVerification;
isLoading: boolean;
setIsLoading: (isLoading: boolean) => void;
mutateUser: KeyedMutator<User>;
sendEmailVerification: typeof sendEmailVerification;
}
const availableDurations = {
"1_month": { label: "1 Month", number: 1 },
"3_months": { label: "3 Months", number: 3 },
"6_months": { label: "6 Months", number: 6 },
"12_months": { label: "12 Months", number: 12 },
"1_month": { label: "1 Month", number: 1 },
"3_months": { label: "3 Months", number: 3 },
"6_months": { label: "6 Months", number: 6 },
"12_months": { label: "12 Months", number: 12 },
};
export default function RegisterCorporate({
isLoading,
setIsLoading,
mutateUser,
sendEmailVerification,
isLoading,
setIsLoading,
mutateUser,
sendEmailVerification,
}: Props) {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [referralAgent, setReferralAgent] = useState<string | undefined>();
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [referralAgent, setReferralAgent] = useState<string | undefined>();
const [companyName, setCompanyName] = useState("");
const [companyUsers, setCompanyUsers] = useState(0);
const [subscriptionDuration, setSubscriptionDuration] = useState(1);
const {acceptedTerms, renderCheckbox} = useAcceptedTerms();
const [companyName, setCompanyName] = useState("");
const [companyUsers, setCompanyUsers] = useState(0);
const [subscriptionDuration, setSubscriptionDuration] = useState(1);
const { acceptedTerms, renderCheckbox } = useAcceptedTerms();
const { users } = useUsers();
const { users } = useUsers();
const onSuccess = () =>
toast.success(
"An e-mail has been sent, please make sure to check your spam folder!",
);
const onSuccess = () =>
toast.success(
"An e-mail has been sent, please make sure to check your spam folder!",
);
const onError = (e: Error) => {
console.error(e);
toast.error("Something went wrong, please logout and re-login.", {
toastId: "send-verify-error",
});
};
const onError = (e: Error) => {
console.error(e);
toast.error("Something went wrong, please logout and re-login.", {
toastId: "send-verify-error",
});
};
const register = (e: any) => {
e.preventDefault();
const register = (e: any) => {
e.preventDefault();
if (confirmPassword !== password) {
toast.error("Your passwords do not match!", {
toastId: "password-not-match",
});
return;
}
if (confirmPassword !== password) {
toast.error("Your passwords do not match!", {
toastId: "password-not-match",
});
return;
}
setIsLoading(true);
axios
.post("/api/register", {
name,
email,
password,
type: "corporate",
profilePicture: "/defaultAvatar.png",
subscriptionExpirationDate: moment().subtract(1, "days").toISOString(),
corporateInformation: {
companyInformation: {
name: companyName,
userAmount: companyUsers,
},
monthlyDuration: subscriptionDuration,
referralAgent,
},
})
.then((response) => {
mutateUser(response.data.user).then(() =>
sendEmailVerification(setIsLoading, onSuccess, onError),
);
})
.catch((error) => {
console.log(error.response.data);
setIsLoading(true);
axios
.post("/api/register", {
name,
email,
password,
type: "corporate",
profilePicture: "/defaultAvatar.png",
subscriptionExpirationDate: moment().subtract(1, "days").toISOString(),
corporateInformation: {
monthlyDuration: subscriptionDuration,
referralAgent,
},
})
.then((response) => {
mutateUser(response.data.user).then(() =>
sendEmailVerification(setIsLoading, onSuccess, onError),
);
})
.catch((error) => {
console.log(error.response.data);
if (error.response.status === 401) {
toast.error("There is already a user with that e-mail!");
return;
}
if (error.response.status === 401) {
toast.error("There is already a user with that e-mail!");
return;
}
if (error.response.status === 400) {
toast.error("The provided code is invalid!");
return;
}
if (error.response.status === 400) {
toast.error("The provided code is invalid!");
return;
}
toast.error("There was something wrong, please try again!");
})
.finally(() => setIsLoading(false));
};
toast.error("There was something wrong, please try again!");
})
.finally(() => setIsLoading(false));
};
return (
<form
className="flex w-full flex-col items-center gap-4"
onSubmit={register}
>
<div className="flex w-full gap-4">
<Input
type="text"
name="name"
onChange={(e) => setName(e)}
placeholder="Enter your name"
defaultValue={name}
required
/>
<Input
type="email"
name="email"
onChange={(e) => setEmail(e.toLowerCase())}
placeholder="Enter email address"
defaultValue={email}
required
/>
</div>
return (
<form
className="flex w-full flex-col items-center gap-4"
onSubmit={register}
>
<div className="flex w-full gap-4">
<Input
type="text"
name="name"
onChange={(e) => setName(e)}
placeholder="Enter your name"
defaultValue={name}
required
/>
<Input
type="email"
name="email"
onChange={(e) => setEmail(e.toLowerCase())}
placeholder="Enter email address"
defaultValue={email}
required
/>
</div>
<div className="flex w-full gap-4">
<Input
type="password"
name="password"
onChange={(e) => setPassword(e)}
placeholder="Enter your password"
defaultValue={password}
required
/>
<Input
type="password"
name="confirmPassword"
onChange={(e) => setConfirmPassword(e)}
placeholder="Confirm your password"
defaultValue={confirmPassword}
required
/>
</div>
<div className="flex w-full gap-4">
<Input
type="password"
name="password"
onChange={(e) => setPassword(e)}
placeholder="Enter your password"
defaultValue={password}
required
/>
<Input
type="password"
name="confirmPassword"
onChange={(e) => setConfirmPassword(e)}
placeholder="Confirm your password"
defaultValue={confirmPassword}
required
/>
</div>
<Divider className="!my-2 w-full" />
<Divider className="!my-2 w-full" />
<div className="flex w-full gap-4">
<Input
type="text"
name="companyName"
onChange={(e) => setCompanyName(e)}
placeholder="Corporate name"
label="Corporate name"
defaultValue={companyName}
required
/>
<Input
type="number"
name="companyUsers"
onChange={(e) => setCompanyUsers(parseInt(e))}
label="Number of users"
defaultValue={companyUsers}
required
/>
</div>
<div className="flex w-full gap-4">
<Input
type="text"
name="companyName"
onChange={(e) => setCompanyName(e)}
placeholder="Corporate name"
label="Corporate name"
defaultValue={companyName}
required
/>
<Input
type="number"
name="companyUsers"
onChange={(e) => setCompanyUsers(parseInt(e))}
label="Number of users"
defaultValue={companyUsers}
required
/>
</div>
<div className="flex w-full gap-4">
<div className="flex w-full flex-col gap-3">
<label className="text-mti-gray-dim text-base font-normal">
Referral *
</label>
<Select
className="placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim border-mti-gray-platinum w-full rounded-full border bg-white px-4 py-4 text-sm font-normal focus:outline-none disabled:cursor-not-allowed"
options={[
{ value: "", label: "No referral" },
...users
.filter((u) => u.type === "agent")
.map((x) => ({ value: x.id, label: `${x.name} - ${x.email}` })),
]}
defaultValue={{ value: "", label: "No referral" }}
onChange={(value) => setReferralAgent(value?.value)}
styles={{
control: (styles) => ({
...styles,
paddingLeft: "4px",
border: "none",
outline: "none",
":focus": {
outline: "none",
},
}),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
/>
</div>
<div className="flex w-full gap-4">
<div className="flex w-full flex-col gap-3">
<label className="text-mti-gray-dim text-base font-normal">
Referral *
</label>
<Select
className="placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim border-mti-gray-platinum w-full rounded-full border bg-white px-4 py-4 text-sm font-normal focus:outline-none disabled:cursor-not-allowed"
options={[
{ value: "", label: "No referral" },
...users
.filter((u) => u.type === "agent")
.map((x) => ({ value: x.id, label: `${x.name} - ${x.email}` })),
]}
defaultValue={{ value: "", label: "No referral" }}
onChange={(value) => setReferralAgent(value?.value)}
styles={{
control: (styles) => ({
...styles,
paddingLeft: "4px",
border: "none",
outline: "none",
":focus": {
outline: "none",
},
}),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
/>
</div>
<div className="flex w-full flex-col gap-3">
<label className="text-mti-gray-dim text-base font-normal">
Subscription Duration *
</label>
<Select
className="placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim border-mti-gray-platinum w-full rounded-full border bg-white px-4 py-4 text-sm font-normal focus:outline-none disabled:cursor-not-allowed"
options={Object.keys(availableDurations).map((value) => ({
value,
label:
availableDurations[value as keyof typeof availableDurations]
.label,
}))}
defaultValue={{
value: "1_month",
label: availableDurations["1_month"].label,
}}
onChange={(value) =>
setSubscriptionDuration(
value
? availableDurations[
value.value as keyof typeof availableDurations
].number
: 1,
)
}
styles={{
control: (styles) => ({
...styles,
paddingLeft: "4px",
border: "none",
outline: "none",
":focus": {
outline: "none",
},
}),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
/>
</div>
</div>
<div className="flex w-full flex-col items-start gap-4">
{renderCheckbox()}
</div>
<Button
className="w-full lg:mt-8"
color="purple"
disabled={
isLoading ||
!email ||
!name ||
!password ||
!confirmPassword ||
password !== confirmPassword ||
!companyName ||
companyUsers <= 0
}
>
Create account
</Button>
</form>
);
<div className="flex w-full flex-col gap-3">
<label className="text-mti-gray-dim text-base font-normal">
Subscription Duration *
</label>
<Select
className="placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim border-mti-gray-platinum w-full rounded-full border bg-white px-4 py-4 text-sm font-normal focus:outline-none disabled:cursor-not-allowed"
options={Object.keys(availableDurations).map((value) => ({
value,
label:
availableDurations[value as keyof typeof availableDurations]
.label,
}))}
defaultValue={{
value: "1_month",
label: availableDurations["1_month"].label,
}}
onChange={(value) =>
setSubscriptionDuration(
value
? availableDurations[
value.value as keyof typeof availableDurations
].number
: 1,
)
}
styles={{
control: (styles) => ({
...styles,
paddingLeft: "4px",
border: "none",
outline: "none",
":focus": {
outline: "none",
},
}),
option: (styles, state) => ({
...styles,
backgroundColor: state.isFocused
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color,
}),
}}
/>
</div>
</div>
<div className="flex w-full flex-col items-start gap-4">
{renderCheckbox()}
</div>
<Button
className="w-full lg:mt-8"
color="purple"
disabled={
isLoading ||
!email ||
!name ||
!password ||
!confirmPassword ||
password !== confirmPassword ||
!companyName ||
companyUsers <= 0
}
>
Create account
</Button>
</form>
);
}

View File

@@ -3,15 +3,15 @@ import Layout from "@/components/High/Layout";
import useGroups from "@/hooks/useGroups";
import usePackages from "@/hooks/usePackages";
import useUsers from "@/hooks/useUsers";
import {User} from "@/interfaces/user";
import { User } from "@/interfaces/user";
import clsx from "clsx";
import {capitalize} from "lodash";
import {useEffect, useState} from "react";
import { capitalize } from "lodash";
import { useEffect, useState } from "react";
import useInvites from "@/hooks/useInvites";
import {BsArrowRepeat} from "react-icons/bs";
import { BsArrowRepeat } from "react-icons/bs";
import InviteCard from "@/components/Medium/InviteCard";
import {useRouter} from "next/router";
import {ToastContainer} from "react-toastify";
import { useRouter } from "next/router";
import { ToastContainer } from "react-toastify";
import useDiscounts from "@/hooks/useDiscounts";
import PaymobPayment from "@/components/PaymobPayment";
import moment from "moment";
@@ -22,17 +22,17 @@ interface Props {
reload: () => void;
}
export default function PaymentDue({user, hasExpired = false, reload}: Props) {
export default function PaymentDue({ user, hasExpired = false, reload }: Props) {
const [isLoading, setIsLoading] = useState(false);
const [appliedDiscount, setAppliedDiscount] = useState(0);
const router = useRouter();
const {packages} = usePackages();
const {discounts} = useDiscounts();
const {users} = useUsers();
const {groups} = useGroups({});
const {invites, isLoading: isInvitesLoading, reload: reloadInvites} = useInvites({to: user?.id});
const { packages } = usePackages();
const { discounts } = useDiscounts();
const { users } = useUsers();
const { groups } = useGroups({});
const { invites, isLoading: isInvitesLoading, reload: reloadInvites } = useInvites({ to: user?.id });
useEffect(() => {
const userDiscounts = discounts.filter((x) => user.email.endsWith(`@${x.domain}`));
@@ -172,7 +172,7 @@ export default function PaymentDue({user, hasExpired = false, reload}: Props) {
<div className="mb-2 flex flex-col items-start">
<img src="/logo_title.png" alt="EnCoach's Logo" className="w-32" />
<span className="text-xl font-semibold">
EnCoach - {user.corporateInformation?.monthlyDuration} Months
EnCoach - {12} Months
</span>
</div>
<div className="flex w-full flex-col items-start gap-2">
@@ -184,7 +184,7 @@ export default function PaymentDue({user, hasExpired = false, reload}: Props) {
setIsPaymentLoading={setIsLoading}
currency={user.corporateInformation.payment.currency}
price={user.corporateInformation.payment.value}
duration={user.corporateInformation.monthlyDuration}
duration={12}
duration_unit="months"
onSuccess={() => {
setIsLoading(false);
@@ -196,8 +196,7 @@ export default function PaymentDue({user, hasExpired = false, reload}: Props) {
<span>This includes:</span>
<ul className="flex flex-col items-start text-sm">
<li>
- Allow a total of {user.corporateInformation.companyInformation.userAmount} students and teachers
to use EnCoach
- Allow a total of 0 students and teachers to use EnCoach
</li>
<li>- Train their abilities for the IELTS exam</li>
<li>- Gain insights into your students&apos; weaknesses and strengths</li>

View File

@@ -13,23 +13,23 @@ import { getStudentGroupsForUsersWithoutAdmin } from "@/utils/groups.be";
import { getSpecificUsers, getUser } from "@/utils/users.be";
import { getUserName } from "@/utils/users";
interface GroupScoreSummaryHelper {
score: [number, number];
label: string;
sessions: string[];
score: [number, number];
label: string;
sessions: string[];
}
interface AssignmentData {
id: string;
assigner: string;
assignees: string[];
results: any;
exams: { module: Module }[];
startDate: string;
excel: {
path: string;
version: string;
};
name: string;
id: string;
assigner: string;
assignees: string[];
results: any;
exams: { module: Module }[];
startDate: string;
excel: {
path: string;
version: string;
};
name: string;
}
const db = client.db(process.env.MONGODB_DB);
@@ -37,427 +37,427 @@ const db = client.db(process.env.MONGODB_DB);
export default withIronSessionApiRoute(handler, sessionOptions);
async function handler(req: NextApiRequest, res: NextApiResponse) {
// if (req.method === "GET") return get(req, res);
if (req.method === "POST") return await post(req, res);
// if (req.method === "GET") return get(req, res);
if (req.method === "POST") return await post(req, res);
}
function logWorksheetData(worksheet: any) {
worksheet.eachRow((row: any, rowNumber: number) => {
console.log(`Row ${rowNumber}:`);
row.eachCell((cell: any, colNumber: number) => {
console.log(` Cell ${colNumber}: ${cell.value}`);
});
});
worksheet.eachRow((row: any, rowNumber: number) => {
console.log(`Row ${rowNumber}:`);
row.eachCell((cell: any, colNumber: number) => {
console.log(` Cell ${colNumber}: ${cell.value}`);
});
});
}
function commonExcel({
data,
userName,
users,
sectionName,
customTable,
customTableHeaders,
renderCustomTableData,
data,
userName,
users,
sectionName,
customTable,
customTableHeaders,
renderCustomTableData,
}: {
data: AssignmentData;
userName: string;
users: User[];
sectionName: string;
customTable: string[][];
customTableHeaders: string[];
renderCustomTableData: (data: any) => string[];
data: AssignmentData;
userName: string;
users: User[];
sectionName: string;
customTable: string[][];
customTableHeaders: string[];
renderCustomTableData: (data: any) => string[];
}) {
const allStats = data.results.flatMap((r: any) => r.stats);
const allStats = data.results.flatMap((r: any) => r.stats);
const uniqueExercises = [...new Set(allStats.map((s: any) => s.exercise))];
const uniqueExercises = [...new Set(allStats.map((s: any) => s.exercise))];
const assigneesData = data.assignees
.map((assignee: string) => {
const userStats = allStats.filter((s: any) => s.user === assignee);
const dates = userStats.map((s: any) => moment(s.date));
const user = users.find((u) => u.id === assignee);
return {
userId: assignee,
// added some default values in case the user is not found
// could it be possible to have an assigned user deleted from the database?
user: user || {
name: "Unknown",
email: "Unknown",
demographicInformation: { passportId: "Unknown", gender: "Unknown" },
},
...userStats.reduce(
(acc: any, curr: any) => {
return {
...acc,
correct: acc.correct + curr.score.correct,
missing: acc.missing + curr.score.missing,
total: acc.total + curr.score.total,
};
},
{ correct: 0, missing: 0, total: 0 }
),
firstDate: moment.min(...dates),
lastDate: moment.max(...dates),
stats: userStats,
};
})
.sort((a, b) => b.correct - a.correct);
const assigneesData = data.assignees
.map((assignee: string) => {
const userStats = allStats.filter((s: any) => s.user === assignee);
const dates = userStats.map((s: any) => moment(s.date));
const user = users.find((u) => u.id === assignee);
return {
userId: assignee,
// added some default values in case the user is not found
// could it be possible to have an assigned user deleted from the database?
user: user || {
name: "Unknown",
email: "Unknown",
demographicInformation: { passportId: "Unknown", gender: "Unknown" },
},
...userStats.reduce(
(acc: any, curr: any) => {
return {
...acc,
correct: acc.correct + curr.score.correct,
missing: acc.missing + curr.score.missing,
total: acc.total + curr.score.total,
};
},
{ correct: 0, missing: 0, total: 0 }
),
firstDate: moment.min(...dates),
lastDate: moment.max(...dates),
stats: userStats,
};
})
.sort((a, b) => b.correct - a.correct);
const results = assigneesData.map((r: any) => r.correct);
const highestScore = Math.max(...results);
const lowestScore = Math.min(...results);
const averageScore = results.reduce((a, b) => a + b, 0) / results.length;
const firstDate = moment.min(assigneesData.map((r: any) => r.firstDate));
const lastDate = moment.max(assigneesData.map((r: any) => r.lastDate));
const results = assigneesData.map((r: any) => r.correct);
const highestScore = Math.max(...results);
const lowestScore = Math.min(...results);
const averageScore = results.reduce((a, b) => a + b, 0) / results.length;
const firstDate = moment.min(assigneesData.map((r: any) => r.firstDate));
const lastDate = moment.max(assigneesData.map((r: any) => r.lastDate));
const firstSectionData = [
{
label: sectionName,
value: userName,
},
{
label: "Report Download date :",
value: moment().format("DD/MM/YYYY"),
},
{ label: "Test Information :", value: data.name },
{
label: "Date of Test :",
value: moment(data.startDate).format("DD/MM/YYYY"),
},
{ label: "Number of Candidates :", value: data.assignees.length },
{ label: "Highest score :", value: highestScore },
{ label: "Lowest score :", value: lowestScore },
{ label: "Average score :", value: averageScore },
{ label: "", value: "" },
{
label: "Date and time of First submission :",
value: firstDate.format("DD/MM/YYYY"),
},
{
label: "Date and time of Last submission :",
value: lastDate.format("DD/MM/YYYY"),
},
];
const firstSectionData = [
{
label: sectionName,
value: userName,
},
{
label: "Report Download date :",
value: moment().format("DD/MM/YYYY"),
},
{ label: "Test Information :", value: data.name },
{
label: "Date of Test :",
value: moment(data.startDate).format("DD/MM/YYYY"),
},
{ label: "Number of Candidates :", value: data.assignees.length },
{ label: "Highest score :", value: highestScore },
{ label: "Lowest score :", value: lowestScore },
{ label: "Average score :", value: averageScore },
{ label: "", value: "" },
{
label: "Date and time of First submission :",
value: firstDate.format("DD/MM/YYYY"),
},
{
label: "Date and time of Last submission :",
value: lastDate.format("DD/MM/YYYY"),
},
];
// Create a new workbook and add a worksheet
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet("Report Data");
// Create a new workbook and add a worksheet
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet("Report Data");
// Populate the worksheet with the data
firstSectionData.forEach(({ label, value }, index) => {
worksheet.getCell(`A${index + 1}`).value = label; // First column (labels)
worksheet.getCell(`B${index + 1}`).value = value; // Second column (values)
});
// Populate the worksheet with the data
firstSectionData.forEach(({ label, value }, index) => {
worksheet.getCell(`A${index + 1}`).value = label; // First column (labels)
worksheet.getCell(`B${index + 1}`).value = value; // Second column (values)
});
// added empty arrays to force row spacings
const customTableAndLine = [[], ...customTable, []];
customTableAndLine.forEach((row: string[], index) => {
worksheet.addRow(row);
});
// added empty arrays to force row spacings
const customTableAndLine = [[], ...customTable, []];
customTableAndLine.forEach((row: string[], index) => {
worksheet.addRow(row);
});
// Define the static part of the headers (before "Test Sections")
const staticHeaders = [
"Sr N",
"Candidate ID",
"First and Last Name",
"Passport/ID",
"Email ID",
"Gender",
...customTableHeaders,
];
// Define the static part of the headers (before "Test Sections")
const staticHeaders = [
"Sr N",
"Candidate ID",
"First and Last Name",
"Passport/ID",
"Email ID",
"Gender",
...customTableHeaders,
];
// Define additional headers after "Test Sections"
const additionalHeaders = ["Time Spent", "Date", "Score"];
// Define additional headers after "Test Sections"
const additionalHeaders = ["Time Spent", "Date", "Score"];
// Calculate the dynamic columns based on the testSectionsArray
const testSectionHeaders = uniqueExercises.map(
(section, index) => `Part ${index + 1}`
);
// Calculate the dynamic columns based on the testSectionsArray
const testSectionHeaders = uniqueExercises.map(
(section, index) => `Part ${index + 1}`
);
const tableColumnHeadersFirstPart = [
...staticHeaders,
...uniqueExercises.map((a) => "Test Sections"),
];
// Add the main header row, merging static columns and "Test Sections"
const tableColumnHeaders = [
...tableColumnHeadersFirstPart,
...additionalHeaders,
];
worksheet.addRow(tableColumnHeaders);
const tableColumnHeadersFirstPart = [
...staticHeaders,
...uniqueExercises.map((a) => "Test Sections"),
];
// Add the main header row, merging static columns and "Test Sections"
const tableColumnHeaders = [
...tableColumnHeadersFirstPart,
...additionalHeaders,
];
worksheet.addRow(tableColumnHeaders);
// 1 headers rows
const startIndexTable =
firstSectionData.length + customTableAndLine.length + 1;
// 1 headers rows
const startIndexTable =
firstSectionData.length + customTableAndLine.length + 1;
// // Merge "Test Sections" over dynamic number of columns
// const tableColumns = staticHeaders.length + numberOfTestSections;
// // Merge "Test Sections" over dynamic number of columns
// const tableColumns = staticHeaders.length + numberOfTestSections;
// K10:M12 = 10,11,12,13
// horizontally group Test Sections
// K10:M12 = 10,11,12,13
// horizontally group Test Sections
// if there are test section headers to even merge:
if (testSectionHeaders.length > 1) {
worksheet.mergeCells(
startIndexTable,
staticHeaders.length + 1,
startIndexTable,
tableColumnHeadersFirstPart.length
);
}
// if there are test section headers to even merge:
if (testSectionHeaders.length > 1) {
worksheet.mergeCells(
startIndexTable,
staticHeaders.length + 1,
startIndexTable,
tableColumnHeadersFirstPart.length
);
}
// Add the dynamic second and third header rows for test sections and sub-columns
worksheet.addRow([
...Array(staticHeaders.length).fill(""),
...testSectionHeaders,
"",
"",
"",
]);
worksheet.addRow([
...Array(staticHeaders.length).fill(""),
...uniqueExercises.map(() => "Grammar & Vocabulary"),
"",
"",
"",
]);
worksheet.addRow([
...Array(staticHeaders.length).fill(""),
...uniqueExercises.map(
(exercise) => allStats.find((s: any) => s.exercise === exercise).type
),
"",
"",
"",
]);
// Add the dynamic second and third header rows for test sections and sub-columns
worksheet.addRow([
...Array(staticHeaders.length).fill(""),
...testSectionHeaders,
"",
"",
"",
]);
worksheet.addRow([
...Array(staticHeaders.length).fill(""),
...uniqueExercises.map(() => "Grammar & Vocabulary"),
"",
"",
"",
]);
worksheet.addRow([
...Array(staticHeaders.length).fill(""),
...uniqueExercises.map(
(exercise) => allStats.find((s: any) => s.exercise === exercise).type
),
"",
"",
"",
]);
// vertically group based on the part, exercise and type
staticHeaders.forEach((header, index) => {
worksheet.mergeCells(
startIndexTable,
index + 1,
startIndexTable + 3,
index + 1
);
});
// vertically group based on the part, exercise and type
staticHeaders.forEach((header, index) => {
worksheet.mergeCells(
startIndexTable,
index + 1,
startIndexTable + 3,
index + 1
);
});
assigneesData.forEach((data, index) => {
worksheet.addRow([
index + 1,
data.userId,
data.user.name,
data.user.demographicInformation?.passportId,
data.user.email,
data.user.demographicInformation?.gender,
...renderCustomTableData(data),
...uniqueExercises.map((exercise) => {
const score = data.stats.find(
(s: any) => s.exercise === exercise && s.user === data.userId
).score;
return `${score.correct}/${score.total}`;
}),
`${Math.ceil(
data.stats.reduce((acc: number, curr: any) => acc + curr.timeSpent, 0) /
60
)} minutes`,
data.lastDate.format("DD/MM/YYYY HH:mm"),
data.correct,
]);
});
assigneesData.forEach((data, index) => {
worksheet.addRow([
index + 1,
data.userId,
data.user.name,
data.user.demographicInformation?.passportId,
data.user.email,
data.user.demographicInformation?.gender,
...renderCustomTableData(data),
...uniqueExercises.map((exercise) => {
const score = data.stats.find(
(s: any) => s.exercise === exercise && s.user === data.userId
).score;
return `${score.correct}/${score.total}`;
}),
`${Math.ceil(
data.stats.reduce((acc: number, curr: any) => acc + curr.timeSpent, 0) /
60
)} minutes`,
data.lastDate.format("DD/MM/YYYY HH:mm"),
data.correct,
]);
});
worksheet.addRow([""]);
worksheet.addRow([""]);
worksheet.addRow([""]);
worksheet.addRow([""]);
for (let i = 0; i < tableColumnHeaders.length; i++) {
worksheet.getColumn(i + 1).width = 30;
}
for (let i = 0; i < tableColumnHeaders.length; i++) {
worksheet.getColumn(i + 1).width = 30;
}
// Apply styles to the headers
[startIndexTable].forEach((rowNumber) => {
worksheet.getRow(rowNumber).eachCell((cell) => {
if (cell.value) {
cell.fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFBFBFBF" }, // Grey color for headers
};
cell.font = { bold: true };
cell.alignment = { vertical: "middle", horizontal: "center" };
}
});
});
// Apply styles to the headers
[startIndexTable].forEach((rowNumber) => {
worksheet.getRow(rowNumber).eachCell((cell) => {
if (cell.value) {
cell.fill = {
type: "pattern",
pattern: "solid",
fgColor: { argb: "FFBFBFBF" }, // Grey color for headers
};
cell.font = { bold: true };
cell.alignment = { vertical: "middle", horizontal: "center" };
}
});
});
worksheet.addRow(["Printed by: Confidential Information"]);
worksheet.addRow(["info@encoach.com"]);
worksheet.addRow(["Printed by: Confidential Information"]);
worksheet.addRow(["info@encoach.com"]);
// Convert workbook to Buffer (Node.js) or Blob (Browser)
return workbook.xlsx.writeBuffer();
// Convert workbook to Buffer (Node.js) or Blob (Browser)
return workbook.xlsx.writeBuffer();
}
function corporateAssignment(
user: CorporateUser,
data: AssignmentData,
users: User[]
user: CorporateUser,
data: AssignmentData,
users: User[]
) {
return commonExcel({
data,
userName: user.corporateInformation?.companyInformation?.name || "",
users,
sectionName: "Corporate Name :",
customTable: [],
customTableHeaders: [],
renderCustomTableData: () => [],
});
return commonExcel({
data,
userName: user.name || "",
users,
sectionName: "Corporate Name :",
customTable: [],
customTableHeaders: [],
renderCustomTableData: () => [],
});
}
async function mastercorporateAssignment(
user: MasterCorporateUser,
data: AssignmentData,
users: User[]
user: MasterCorporateUser,
data: AssignmentData,
users: User[]
) {
const userGroups = await getStudentGroupsForUsersWithoutAdmin(
user.id,
data.assignees
);
const adminUsers = [...new Set(userGroups.map((g) => g.admin))];
const userGroups = await getStudentGroupsForUsersWithoutAdmin(
user.id,
data.assignees
);
const adminUsers = [...new Set(userGroups.map((g) => g.admin))];
const userGroupsParticipants = userGroups.flatMap((g) => g.participants);
const adminsData = await getSpecificUsers(adminUsers);
const companiesData = adminsData.map((user) => {
const name = getUserName(user);
const users = userGroupsParticipants.filter((p) =>
data.assignees.includes(p)
);
const userGroupsParticipants = userGroups.flatMap((g) => g.participants);
const adminsData = await getSpecificUsers(adminUsers);
const companiesData = adminsData.map((user) => {
const name = getUserName(user);
const users = userGroupsParticipants.filter((p) =>
data.assignees.includes(p)
);
const stats = data.results
.flatMap((r: any) => r.stats)
.filter((s: any) => users.includes(s.user));
const correct = stats.reduce(
(acc: number, s: any) => acc + s.score.correct,
0
);
const total = stats.reduce(
(acc: number, curr: any) => acc + curr.score.total,
0
);
const stats = data.results
.flatMap((r: any) => r.stats)
.filter((s: any) => users.includes(s.user));
const correct = stats.reduce(
(acc: number, s: any) => acc + s.score.correct,
0
);
const total = stats.reduce(
(acc: number, curr: any) => acc + curr.score.total,
0
);
return {
name,
correct,
total,
};
});
return {
name,
correct,
total,
};
});
const customTable = [
...companiesData,
{
name: "Total",
correct: companiesData.reduce((acc, curr) => acc + curr.correct, 0),
total: companiesData.reduce((acc, curr) => acc + curr.total, 0),
},
].map((c) => [c.name, `${c.correct}/${c.total}`]);
const customTable = [
...companiesData,
{
name: "Total",
correct: companiesData.reduce((acc, curr) => acc + curr.correct, 0),
total: companiesData.reduce((acc, curr) => acc + curr.total, 0),
},
].map((c) => [c.name, `${c.correct}/${c.total}`]);
const customTableHeaders = [
{ name: "Corporate", helper: (data: any) => data.user.corporateName },
];
return commonExcel({
data,
userName: user.corporateInformation?.companyInformation?.name || "",
users: users.map((u) => {
const userGroup = userGroups.find((g) => g.participants.includes(u.id));
const admin = adminsData.find((a) => a.id === userGroup?.admin);
return {
...u,
corporateName: getUserName(admin),
};
}),
sectionName: "Master Corporate Name :",
customTable: [["Corporate Summary"], ...customTable],
customTableHeaders: customTableHeaders.map((h) => h.name),
renderCustomTableData: (data) =>
customTableHeaders.map((h) => h.helper(data)),
});
const customTableHeaders = [
{ name: "Corporate", helper: (data: any) => data.user.corporateName },
];
return commonExcel({
data,
userName: user.name || "",
users: users.map((u) => {
const userGroup = userGroups.find((g) => g.participants.includes(u.id));
const admin = adminsData.find((a) => a.id === userGroup?.admin);
return {
...u,
corporateName: getUserName(admin),
};
}),
sectionName: "Master Corporate Name :",
customTable: [["Corporate Summary"], ...customTable],
customTableHeaders: customTableHeaders.map((h) => h.name),
renderCustomTableData: (data) =>
customTableHeaders.map((h) => h.helper(data)),
});
}
async function post(req: NextApiRequest, res: NextApiResponse) {
// verify if it's a logged user that is trying to export
if (req.session.user) {
const { id } = req.query as { id: string };
// verify if it's a logged user that is trying to export
if (req.session.user) {
const { id } = req.query as { id: string };
const assignment = await db.collection("assignments").findOne<AssignmentData>({ id: id });
if (!assignment) {
res.status(400).end();
return;
}
const assignment = await db.collection("assignments").findOne<AssignmentData>({ id: id });
if (!assignment) {
res.status(400).end();
return;
}
// if (
// data.excel &&
// data.excel.path &&
// data.excel.version === process.env.EXCEL_VERSION
// ) {
// // if it does, return the excel url
// const fileRef = ref(storage, data.excel.path);
// const url = await getDownloadURL(fileRef);
// res.status(200).end(url);
// return;
// }
// if (
// data.excel &&
// data.excel.path &&
// data.excel.version === process.env.EXCEL_VERSION
// ) {
// // if it does, return the excel url
// const fileRef = ref(storage, data.excel.path);
// const url = await getDownloadURL(fileRef);
// res.status(200).end(url);
// return;
// }
const objectIds = assignment.assignees.map(id => id);
const objectIds = assignment.assignees.map(id => id);
const users = await db.collection("users").find<User>({
id: { $in: objectIds }
}).toArray();
const users = await db.collection("users").find<User>({
id: { $in: objectIds }
}).toArray();
const user = await db.collection("users").findOne<User>({ id: assignment.assigner });
const user = await db.collection("users").findOne<User>({ id: assignment.assigner });
// we'll need the user in order to get the user data (name, email, focus, etc);
if (user && users) {
// generate the file ref for storage
const fileName = `${Date.now().toString()}.xlsx`;
const refName = `assignment_report/${fileName}`;
const fileRef = ref(storage, refName);
// we'll need the user in order to get the user data (name, email, focus, etc);
if (user && users) {
// generate the file ref for storage
const fileName = `${Date.now().toString()}.xlsx`;
const refName = `assignment_report/${fileName}`;
const fileRef = ref(storage, refName);
const getExcelFn = () => {
switch (user.type) {
case "teacher":
case "corporate":
return corporateAssignment(user as CorporateUser, assignment, users);
case "mastercorporate":
return mastercorporateAssignment(
user as MasterCorporateUser,
assignment,
users
);
default:
throw new Error("Invalid user type");
}
};
const buffer = await getExcelFn();
const getExcelFn = () => {
switch (user.type) {
case "teacher":
case "corporate":
return corporateAssignment(user as CorporateUser, assignment, users);
case "mastercorporate":
return mastercorporateAssignment(
user as MasterCorporateUser,
assignment,
users
);
default:
throw new Error("Invalid user type");
}
};
const buffer = await getExcelFn();
// upload the pdf to storage
await uploadBytes(fileRef, buffer, {
contentType:
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
// upload the pdf to storage
await uploadBytes(fileRef, buffer, {
contentType:
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
// update the stats entries with the pdf url to prevent duplication
await db.collection("assignments").updateOne(
{ id: assignment.id },
{
$set: {
excel: {
path: refName,
version: process.env.EXCEL_VERSION,
}
}
}
);
// update the stats entries with the pdf url to prevent duplication
await db.collection("assignments").updateOne(
{ id: assignment.id },
{
$set: {
excel: {
path: refName,
version: process.env.EXCEL_VERSION,
}
}
}
);
const url = await getDownloadURL(fileRef);
res.status(200).end(url);
const url = await getDownloadURL(fileRef);
res.status(200).end(url);
return;
}
}
return;
}
}
res.status(401).json({ message: "Unauthorized" });
res.status(401).json({ message: "Unauthorized" });
}

View File

@@ -310,19 +310,17 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
if (groups.length > 0) {
const admins = await db.collection("users")
.find<CorporateUser>({ id: { $in: groups.map(g => g.admin).map(id => id)} })
.find<CorporateUser>({ id: { $in: groups.map(g => g.admin).map(id => id) } })
.toArray();
const adminData = admins.find((a) => a.corporateInformation?.companyInformation?.name);
const adminData = admins.find((a) => a.name);
if (adminData) {
return adminData.corporateInformation.companyInformation.name;
return adminData.name;
}
}
}
if (assignerUser.type === "corporate" && assignerUser.corporateInformation?.companyInformation?.name) {
return assignerUser.corporateInformation.companyInformation.name;
}
return assignerUser.type
}
} catch (err) {
console.error(err);
@@ -372,16 +370,16 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
// update the stats entries with the pdf url to prevent duplication
await db.collection("assignments").updateOne(
{ id: data.id },
{
$set: {
pdf: {
path: refName,
version: process.env.PDF_VERSION,
{
$set: {
pdf: {
path: refName,
version: process.env.PDF_VERSION,
}
}
}
}
);
);
const url = await getDownloadURL(fileRef);
res.status(200).end(url);
return;

View File

@@ -62,7 +62,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
if (req.session.user.type === "corporate") {
const totalCodes = userCodes.filter((x) => !x.userId || !usersInGroups.includes(x.userId)).length + usersInGroups.length + codes.length;
const allowedCodes = req.session.user.corporateInformation?.companyInformation.userAmount || 0;
const allowedCodes = 0;
if (totalCodes > allowedCodes) {
res.status(403).json({
@@ -127,7 +127,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
// upsert: true -> if it doesnt exist insert
await db.collection("codes").updateOne(
{ id: code },
{ $set: { id: code, ...codeInformation} },
{ $set: { id: code, ...codeInformation } },
{ upsert: true }
);
}

View File

@@ -1,13 +1,14 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type {NextApiRequest, NextApiResponse} from "next";
import {withIronSessionApiRoute} from "iron-session/next";
import {sessionOptions} from "@/lib/session";
import {deleteEntity, getEntity, getEntityWithRoles} from "@/utils/entities.be";
import type { NextApiRequest, NextApiResponse } from "next";
import { withIronSessionApiRoute } from "iron-session/next";
import { sessionOptions } from "@/lib/session";
import { deleteEntity, getEntity, getEntityWithRoles } from "@/utils/entities.be";
import client from "@/lib/mongodb";
import {Entity} from "@/interfaces/entity";
import { Entity } from "@/interfaces/entity";
import { doesEntityAllow } from "@/utils/permissions";
import { getUser } from "@/utils/users.be";
import { requestUser } from "@/utils/api";
import { isAdmin } from "@/utils/users";
const db = client.db(process.env.MONGODB_DB);
@@ -23,7 +24,7 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
const user = await requestUser(req, res)
if (!user) return res.status(401).json({ ok: false });
const {id, showRoles} = req.query as {id: string; showRoles: string};
const { id, showRoles } = req.query as { id: string; showRoles: string };
const entity = await (!!showRoles ? getEntityWithRoles : getEntity)(id);
res.status(200).json(entity);
@@ -39,23 +40,31 @@ async function del(req: NextApiRequest, res: NextApiResponse) {
if (!entity) return res.status(404).json({ ok: false })
if (!doesEntityAllow(user, entity, "delete_entity") && !["admin", "developer"].includes(user.type))
return res.status(403).json({ok: false})
return res.status(403).json({ ok: false })
await deleteEntity(entity)
return res.status(200).json({ok: true});
return res.status(200).json({ ok: true });
}
async function patch(req: NextApiRequest, res: NextApiResponse) {
const user = await requestUser(req, res)
if (!user) return res.status(401).json({ ok: false });
if (!user) return res.status(401).json({ ok: false });
const {id} = req.query as {id: string};
const { id } = req.query as { id: string };
if (!user.entities.map((x) => x.id).includes(id)) {
return res.status(403).json({ok: false});
if (!user.entities.map((x) => x.id).includes(id) && !isAdmin(user)) {
return res.status(403).json({ ok: false });
}
const entity = await db.collection<Entity>("entities").updateOne({id}, {$set: {label: req.body.label}});
if (req.body.label) {
const entity = await db.collection<Entity>("entities").updateOne({ id }, { $set: { label: req.body.label } });
return res.status(200).json({ ok: entity.acknowledged });
}
return res.status(200).json({ok: entity.acknowledged});
if (req.body.licenses) {
const entity = await db.collection<Entity>("entities").updateOne({ id }, { $set: { licenses: req.body.licenses } });
return res.status(200).json({ ok: entity.acknowledged });
}
return res.status(200).json({ ok: true });
}

View File

@@ -1,10 +1,10 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type {NextApiRequest, NextApiResponse} from "next";
import {withIronSessionApiRoute} from "iron-session/next";
import {sessionOptions} from "@/lib/session";
import {addUsersToEntity, addUserToEntity, createEntity, getEntities, getEntitiesWithRoles} from "@/utils/entities.be";
import {Entity} from "@/interfaces/entity";
import {v4} from "uuid";
import type { NextApiRequest, NextApiResponse } from "next";
import { withIronSessionApiRoute } from "iron-session/next";
import { sessionOptions } from "@/lib/session";
import { addUsersToEntity, addUserToEntity, createEntity, getEntities, getEntitiesWithRoles } from "@/utils/entities.be";
import { Entity } from "@/interfaces/entity";
import { v4 } from "uuid";
import { requestUser } from "@/utils/api";
export default withIronSessionApiRoute(handler, sessionOptions);
@@ -18,7 +18,7 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
const user = await requestUser(req, res)
if (!user) return res.status(401).json({ ok: false });
const {showRoles} = req.query as {showRoles: string};
const { showRoles } = req.query as { showRoles: string };
const getFn = showRoles ? getEntitiesWithRoles : getEntities;
@@ -31,12 +31,13 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
if (!user) return res.status(401).json({ ok: false });
if (!["admin", "developer"].includes(user.type)) {
return res.status(403).json({ok: false});
return res.status(403).json({ ok: false });
}
const entity: Entity = {
id: v4(),
label: req.body.label,
licenses: req.body.licenses
};
const members = req.body.members as string[] | undefined || []

View File

@@ -1,6 +1,6 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import client from "@/lib/mongodb";
import type {NextApiRequest, NextApiResponse} from "next";
import type { NextApiRequest, NextApiResponse } from "next";
const db = client.db(process.env.MONGODB_DB);
@@ -9,8 +9,5 @@ type Data = {
};
export default async function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
// await db.collection("users").updateMany({}, {$set: {entities: []}});
await db.collection("invites").deleteMany({});
res.status(200).json({name: "John Doe"});
res.status(200).json({ name: "John Doe" });
}

View File

@@ -14,17 +14,17 @@ import { getEntityWithRoles } from "@/utils/entities.be";
import { findBy } from "@/utils";
const DEFAULT_DESIRED_LEVELS = {
reading: 9,
listening: 9,
writing: 9,
speaking: 9,
reading: 9,
listening: 9,
writing: 9,
speaking: 9,
};
const DEFAULT_LEVELS = {
reading: 0,
listening: 0,
writing: 0,
speaking: 0,
reading: 0,
listening: 0,
writing: 0,
speaking: 0,
};
const auth = getAuth(app);
@@ -33,99 +33,94 @@ const db = client.db(process.env.MONGODB_DB);
export default withIronSessionApiRoute(handler, sessionOptions);
async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") return post(req, res);
if (req.method === "POST") return post(req, res);
return res.status(404).json({ ok: false });
return res.status(404).json({ ok: false });
}
async function post(req: NextApiRequest, res: NextApiResponse) {
const maker = req.session.user;
if (!maker) {
return res.status(401).json({ ok: false, reason: "You must be logged in to make user!" });
}
const maker = req.session.user;
if (!maker) {
return res.status(401).json({ ok: false, reason: "You must be logged in to make user!" });
}
const { email, passport_id, password, type, groupID, entity, expiryDate, corporate } = req.body as {
email: string;
password?: string;
passport_id: string;
type: string;
entity: string;
groupID?: string;
corporate?: string;
expiryDate: null | Date;
};
const { email, passport_id, password, type, groupID, entity, expiryDate, corporate } = req.body as {
email: string;
password?: string;
passport_id: string;
type: string;
entity: string;
groupID?: string;
corporate?: string;
expiryDate: null | Date;
};
// cleaning data
delete req.body.passport_id;
delete req.body.groupID;
delete req.body.expiryDate;
delete req.body.password;
delete req.body.corporate;
delete req.body.entity
// cleaning data
delete req.body.passport_id;
delete req.body.groupID;
delete req.body.expiryDate;
delete req.body.password;
delete req.body.corporate;
delete req.body.entity
await createUserWithEmailAndPassword(auth, email.toLowerCase(), !!password ? password : passport_id)
.then(async (userCredentials) => {
const userId = userCredentials.user.uid;
await createUserWithEmailAndPassword(auth, email.toLowerCase(), !!password ? password : passport_id)
.then(async (userCredentials) => {
const userId = userCredentials.user.uid;
const entityWithRole = await getEntityWithRoles(entity)
const defaultRole = findBy(entityWithRole?.roles || [], "isDefault", true)
const entityWithRole = await getEntityWithRoles(entity)
const defaultRole = findBy(entityWithRole?.roles || [], "isDefault", true)
const user = {
...req.body,
bio: "",
id: userId,
type: type,
focus: "academic",
status: "active",
desiredLevels: DEFAULT_DESIRED_LEVELS,
profilePicture: "/defaultAvatar.png",
levels: DEFAULT_LEVELS,
isFirstLogin: false,
isVerified: true,
registrationDate: new Date(),
entities: [{ id: entity, role: defaultRole?.id || "" }],
subscriptionExpirationDate: expiryDate || null,
...((maker.type === "corporate" || maker.type === "mastercorporate") && type === "corporate"
? {
corporateInformation: {
companyInformation: {
name: maker.corporateInformation?.companyInformation?.name || "N/A",
userAmount: 0,
},
},
}
: {}),
};
const user = {
...req.body,
bio: "",
id: userId,
type: type,
focus: "academic",
status: "active",
desiredLevels: DEFAULT_DESIRED_LEVELS,
profilePicture: "/defaultAvatar.png",
levels: DEFAULT_LEVELS,
isFirstLogin: false,
isVerified: true,
registrationDate: new Date(),
entities: [{ id: entity, role: defaultRole?.id || "" }],
subscriptionExpirationDate: expiryDate || null,
...((maker.type === "corporate" || maker.type === "mastercorporate") && type === "corporate"
? {
corporateInformation: {},
}
: {}),
};
const uid = new ShortUniqueId();
const code = uid.randomUUID(6);
const uid = new ShortUniqueId();
const code = uid.randomUUID(6);
await db.collection("users").insertOne(user);
await db.collection("codes").insertOne({
code,
creator: maker.id,
expiryDate,
type,
creationDate: new Date(),
userId,
email: email.toLowerCase(),
name: req.body.name,
...(!!passport_id ? { passport_id } : {}),
});
await db.collection("users").insertOne(user);
await db.collection("codes").insertOne({
code,
creator: maker.id,
expiryDate,
type,
creationDate: new Date(),
userId,
email: email.toLowerCase(),
name: req.body.name,
...(!!passport_id ? { passport_id } : {}),
});
if (!!groupID) {
const group = await getGroup(groupID);
if (!!group) await db.collection("groups").updateOne({ id: group.id }, { $set: { participants: [...group.participants, userId] } });
}
if (!!groupID) {
const group = await getGroup(groupID);
if (!!group) await db.collection("groups").updateOne({ id: group.id }, { $set: { participants: [...group.participants, userId] } });
}
console.log(`Returning - ${email}`);
return res.status(200).json({ ok: true });
})
.catch((error) => {
if (error.code.includes("email-already-in-use")) return res.status(403).json({ error, message: "E-mail is already in the platform." });
console.log(`Returning - ${email}`);
return res.status(200).json({ ok: true });
})
.catch((error) => {
if (error.code.includes("email-already-in-use")) return res.status(403).json({ error, message: "E-mail is already in the platform." });
console.log(`Failing - ${email}`);
console.log(error);
return res.status(401).json({ error });
});
console.log(`Failing - ${email}`);
console.log(error);
return res.status(401).json({ error });
});
}

View File

@@ -58,7 +58,7 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
if (admin) {
return {
...d,
corporate: admin.corporateInformation?.companyInformation?.name,
corporate: admin.name,
};
}

View File

@@ -1,7 +1,7 @@
import Layout from "@/components/High/Layout";
import Separator from "@/components/Low/Separator";
import AssignmentCard from "@/dashboards/AssignmentCard";
import AssignmentView from "@/dashboards/AssignmentView";
import AssignmentCard from "@/components/AssignmentCard";
import AssignmentView from "@/components/AssignmentView";
import { useAllowedEntities } from "@/hooks/useEntityPermissions";
import { useListSearch } from "@/hooks/useListSearch";
import usePagination from "@/hooks/usePagination";

View File

@@ -1,7 +1,7 @@
/* eslint-disable @next/next/no-img-element */
import Layout from "@/components/High/Layout";
import UserDisplayList from "@/components/UserDisplayList";
import IconCard from "@/dashboards/IconCard";
import IconCard from "@/components/IconCard";
import { Module } from "@/interfaces";
import { EntityWithRoles } from "@/interfaces/entity";
import { Assignment } from "@/interfaces/results";

View File

@@ -1,7 +1,7 @@
/* eslint-disable @next/next/no-img-element */
import Layout from "@/components/High/Layout";
import UserDisplayList from "@/components/UserDisplayList";
import IconCard from "@/dashboards/IconCard";
import IconCard from "@/components/IconCard";
import { Module } from "@/interfaces";
import { EntityWithRoles } from "@/interfaces/entity";
import { Assignment } from "@/interfaces/results";
@@ -25,183 +25,183 @@ import Head from "next/head";
import { useRouter } from "next/router";
import { useMemo } from "react";
import {
BsClipboard2Data,
BsClock,
BsEnvelopePaper,
BsPaperclip,
BsPencilSquare,
BsPeople,
BsPeopleFill,
BsPersonFill,
BsPersonFillGear,
BsClipboard2Data,
BsClock,
BsEnvelopePaper,
BsPaperclip,
BsPencilSquare,
BsPeople,
BsPeopleFill,
BsPersonFill,
BsPersonFillGear,
} from "react-icons/bs";
import { ToastContainer } from "react-toastify";
interface Props {
user: User;
users: User[];
entities: EntityWithRoles[];
assignments: Assignment[];
stats: Stat[];
groups: Group[];
user: User;
users: User[];
entities: EntityWithRoles[];
assignments: Assignment[];
stats: Stat[];
groups: Group[];
}
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res)
if (!user) return redirect("/login")
if (!checkAccess(user, ["admin", "developer", "corporate"])) return redirect("/")
if (!checkAccess(user, ["admin", "developer", "corporate"])) return redirect("/")
const entityIDS = mapBy(user.entities, "id") || [];
const entities = await getEntitiesWithRoles(entityIDS);
const users = await filterAllowedUsers(user, entities)
const entityIDS = mapBy(user.entities, "id") || [];
const entities = await getEntitiesWithRoles(entityIDS);
const users = await filterAllowedUsers(user, entities)
const assignments = await getEntitiesAssignments(entityIDS);
const stats = await getStatsByUsers(users.map((u) => u.id));
const groups = await getGroupsByEntities(entityIDS);
const assignments = await getEntitiesAssignments(entityIDS);
const stats = await getStatsByUsers(users.map((u) => u.id));
const groups = await getGroupsByEntities(entityIDS);
return { props: serialize({ user, users, entities, assignments, stats, groups }) };
return { props: serialize({ user, users, entities, assignments, stats, groups }) };
}, sessionOptions);
export default function Dashboard({ user, users, entities, assignments, stats, groups }: Props) {
const students = useMemo(() => users.filter((u) => u.type === "student"), [users]);
const teachers = useMemo(() => users.filter((u) => u.type === "teacher"), [users]);
const students = useMemo(() => users.filter((u) => u.type === "student"), [users]);
const teachers = useMemo(() => users.filter((u) => u.type === "teacher"), [users]);
const router = useRouter();
const router = useRouter();
const averageLevelCalculator = (studentStats: Stat[]) => {
const formattedStats = studentStats
.map((s) => ({
focus: students.find((u) => u.id === s.user)?.focus,
score: s.score,
module: s.module,
}))
.filter((f) => !!f.focus);
const bandScores = formattedStats.map((s) => ({
module: s.module,
level: calculateBandScore(s.score.correct, s.score.total, s.module, s.focus!),
}));
const averageLevelCalculator = (studentStats: Stat[]) => {
const formattedStats = studentStats
.map((s) => ({
focus: students.find((u) => u.id === s.user)?.focus,
score: s.score,
module: s.module,
}))
.filter((f) => !!f.focus);
const bandScores = formattedStats.map((s) => ({
module: s.module,
level: calculateBandScore(s.score.correct, s.score.total, s.module, s.focus!),
}));
const levels: { [key in Module]: number } = {
reading: 0,
listening: 0,
writing: 0,
speaking: 0,
level: 0,
};
bandScores.forEach((b) => (levels[b.module] += b.level));
const levels: { [key in Module]: number } = {
reading: 0,
listening: 0,
writing: 0,
speaking: 0,
level: 0,
};
bandScores.forEach((b) => (levels[b.module] += b.level));
return calculateAverageLevel(levels);
};
return calculateAverageLevel(levels);
};
const UserDisplay = (displayUser: User) => (
<div className="flex w-full p-4 gap-4 items-center hover:bg-mti-purple-ultralight cursor-pointer transition ease-in-out duration-300">
<img src={displayUser.profilePicture} alt={displayUser.name} className="rounded-full w-10 h-10" />
<div className="flex flex-col gap-1 items-start">
<span>{displayUser.name}</span>
<span className="text-sm opacity-75">{displayUser.email}</span>
</div>
</div>
);
const UserDisplay = (displayUser: User) => (
<div className="flex w-full p-4 gap-4 items-center hover:bg-mti-purple-ultralight cursor-pointer transition ease-in-out duration-300">
<img src={displayUser.profilePicture} alt={displayUser.name} className="rounded-full w-10 h-10" />
<div className="flex flex-col gap-1 items-start">
<span>{displayUser.name}</span>
<span className="text-sm opacity-75">{displayUser.email}</span>
</div>
</div>
);
return (
<>
<Head>
<title>EnCoach</title>
<meta
name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<ToastContainer />
<Layout user={user}>
<div className="w-full flex flex-col gap-4">
{entities.length > 0 && (
<div className="w-fit self-end bg-neutral-200 px-2 rounded-lg py-1">
<b>{mapBy(entities, "label")?.join(", ")}</b>
</div>
)}
<section className="grid grid-cols-5 -md:grid-cols-2 place-items-center gap-4 text-center">
<IconCard
onClick={() => router.push("/users?type=student")}
Icon={BsPersonFill}
label="Students"
value={students.length}
color="purple"
/>
<IconCard
onClick={() => router.push("/users?type=teacher")}
Icon={BsPencilSquare}
label="Teachers"
value={teachers.length}
color="purple"
/>
<IconCard
onClick={() => router.push("/classrooms")}
Icon={BsPeople}
label="Classrooms"
value={groups.length}
color="purple"
/>
<IconCard Icon={BsPeopleFill}
onClick={() => router.push("/entities")}
label="Entities"
value={entities.length}
color="purple"
/>
<IconCard Icon={BsClipboard2Data} label="Exams Performed" value={uniqBy(stats, "exam").length} color="purple" />
<IconCard Icon={BsPaperclip} label="Average Level" value={averageLevelCalculator(stats).toFixed(1)} color="purple" />
<IconCard Icon={BsPersonFillGear}
onClick={() => router.push("/users/performance")}
label="Student Performance"
return (
<>
<Head>
<title>EnCoach</title>
<meta
name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<ToastContainer />
<Layout user={user}>
<div className="w-full flex flex-col gap-4">
{entities.length > 0 && (
<div className="w-fit self-end bg-neutral-200 px-2 rounded-lg py-1">
<b>{mapBy(entities, "label")?.join(", ")}</b>
</div>
)}
<section className="grid grid-cols-5 -md:grid-cols-2 place-items-center gap-4 text-center">
<IconCard
onClick={() => router.push("/users?type=student")}
Icon={BsPersonFill}
label="Students"
value={students.length}
color="purple"
/>
<IconCard
Icon={BsClock}
label="Expiration Date"
value={user.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).format("DD/MM/yyyy") : "Unlimited"}
color="rose"
/>
<IconCard
Icon={BsEnvelopePaper}
className="col-span-2"
onClick={() => router.push("/assignments")}
label="Assignments"
value={assignments.filter((a) => !a.archived).length}
color="purple"
/>
</section>
</div>
<IconCard
onClick={() => router.push("/users?type=teacher")}
Icon={BsPencilSquare}
label="Teachers"
value={teachers.length}
color="purple"
/>
<IconCard
onClick={() => router.push("/classrooms")}
Icon={BsPeople}
label="Classrooms"
value={groups.length}
color="purple"
/>
<IconCard Icon={BsPeopleFill}
onClick={() => router.push("/entities")}
label="Entities"
value={entities.length}
color="purple"
/>
<IconCard Icon={BsClipboard2Data} label="Exams Performed" value={uniqBy(stats, "exam").length} color="purple" />
<IconCard Icon={BsPaperclip} label="Average Level" value={averageLevelCalculator(stats).toFixed(1)} color="purple" />
<IconCard Icon={BsPersonFillGear}
onClick={() => router.push("/users/performance")}
label="Student Performance"
value={students.length}
color="purple"
/>
<IconCard
Icon={BsClock}
label="Expiration Date"
value={user.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).format("DD/MM/yyyy") : "Unlimited"}
color="rose"
/>
<IconCard
Icon={BsEnvelopePaper}
className="col-span-2"
onClick={() => router.push("/assignments")}
label="Assignments"
value={assignments.filter((a) => !a.archived).length}
color="purple"
/>
</section>
</div>
<section className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full justify-between">
<UserDisplayList
users={students.sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))}
title="Latest Students"
/>
<UserDisplayList
users={teachers.sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))}
title="Latest Teachers"
/>
<UserDisplayList
users={students.sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels))}
title="Highest level students"
/>
<UserDisplayList
users={
students
.sort(
(a, b) =>
Object.keys(groupByExam(filterBy(stats, "user", b))).length -
Object.keys(groupByExam(filterBy(stats, "user", a))).length,
)
}
title="Highest exam count students"
/>
</section>
</Layout>
</>
);
<section className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full justify-between">
<UserDisplayList
users={students.sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))}
title="Latest Students"
/>
<UserDisplayList
users={teachers.sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))}
title="Latest Teachers"
/>
<UserDisplayList
users={students.sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels))}
title="Highest level students"
/>
<UserDisplayList
users={
students
.sort(
(a, b) =>
Object.keys(groupByExam(filterBy(stats, "user", b))).length -
Object.keys(groupByExam(filterBy(stats, "user", a))).length,
)
}
title="Highest exam count students"
/>
</section>
</Layout>
</>
);
}

View File

@@ -1,7 +1,7 @@
/* eslint-disable @next/next/no-img-element */
import Layout from "@/components/High/Layout";
import UserDisplayList from "@/components/UserDisplayList";
import IconCard from "@/dashboards/IconCard";
import IconCard from "@/components/IconCard";
import { Module } from "@/interfaces";
import { EntityWithRoles } from "@/interfaces/entity";
import { Assignment } from "@/interfaces/results";

View File

@@ -1,7 +1,7 @@
/* eslint-disable @next/next/no-img-element */
import Layout from "@/components/High/Layout";
import UserDisplayList from "@/components/UserDisplayList";
import IconCard from "@/dashboards/IconCard";
import IconCard from "@/components/IconCard";
import { useAllowedEntities } from "@/hooks/useEntityPermissions";
import { Module } from "@/interfaces";
import { EntityWithRoles } from "@/interfaces/entity";

View File

@@ -1,7 +1,7 @@
/* eslint-disable @next/next/no-img-element */
import Layout from "@/components/High/Layout";
import UserDisplayList from "@/components/UserDisplayList";
import IconCard from "@/dashboards/IconCard";
import IconCard from "@/components/IconCard";
import { Module } from "@/interfaces";
import { EntityWithRoles } from "@/interfaces/entity";
import { Assignment } from "@/interfaces/results";
@@ -28,139 +28,139 @@ import { useAllowedEntities } from "@/hooks/useEntityPermissions";
import { filterAllowedUsers } from "@/utils/users.be";
interface Props {
user: User;
users: User[];
entities: EntityWithRoles[];
assignments: Assignment[];
stats: Stat[];
groups: Group[];
user: User;
users: User[];
entities: EntityWithRoles[];
assignments: Assignment[];
stats: Stat[];
groups: Group[];
}
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res)
if (!user) return redirect("/login")
if (!checkAccess(user, ["admin", "developer", "teacher"]))
return redirect("/")
if (!checkAccess(user, ["admin", "developer", "teacher"]))
return redirect("/")
const entityIDS = mapBy(user.entities, "id") || [];
const entities = await getEntitiesWithRoles(entityIDS);
const users = await filterAllowedUsers(user, entities)
const entityIDS = mapBy(user.entities, "id") || [];
const entities = await getEntitiesWithRoles(entityIDS);
const users = await filterAllowedUsers(user, entities)
const assignments = await getEntitiesAssignments(entityIDS);
const stats = await getStatsByUsers(users.map((u) => u.id));
const groups = await getGroupsByEntities(entityIDS);
const assignments = await getEntitiesAssignments(entityIDS);
const stats = await getStatsByUsers(users.map((u) => u.id));
const groups = await getGroupsByEntities(entityIDS);
return { props: serialize({ user, users, entities, assignments, stats, groups }) };
return { props: serialize({ user, users, entities, assignments, stats, groups }) };
}, sessionOptions);
export default function Dashboard({ user, users, entities, assignments, stats, groups }: Props) {
const students = useMemo(() => users.filter((u) => u.type === "student"), [users]);
const router = useRouter();
const students = useMemo(() => users.filter((u) => u.type === "student"), [users]);
const router = useRouter();
const averageLevelCalculator = (studentStats: Stat[]) => {
const formattedStats = studentStats
.map((s) => ({
focus: students.find((u) => u.id === s.user)?.focus,
score: s.score,
module: s.module,
}))
.filter((f) => !!f.focus);
const bandScores = formattedStats.map((s) => ({
module: s.module,
level: calculateBandScore(s.score.correct, s.score.total, s.module, s.focus!),
}));
const averageLevelCalculator = (studentStats: Stat[]) => {
const formattedStats = studentStats
.map((s) => ({
focus: students.find((u) => u.id === s.user)?.focus,
score: s.score,
module: s.module,
}))
.filter((f) => !!f.focus);
const bandScores = formattedStats.map((s) => ({
module: s.module,
level: calculateBandScore(s.score.correct, s.score.total, s.module, s.focus!),
}));
const levels: { [key in Module]: number } = {
reading: 0,
listening: 0,
writing: 0,
speaking: 0,
level: 0,
};
bandScores.forEach((b) => (levels[b.module] += b.level));
const levels: { [key in Module]: number } = {
reading: 0,
listening: 0,
writing: 0,
speaking: 0,
level: 0,
};
bandScores.forEach((b) => (levels[b.module] += b.level));
return calculateAverageLevel(levels);
};
return calculateAverageLevel(levels);
};
const UserDisplay = (displayUser: User) => (
<div className="flex w-full p-4 gap-4 items-center hover:bg-mti-purple-ultralight cursor-pointer transition ease-in-out duration-300">
<img src={displayUser.profilePicture} alt={displayUser.name} className="rounded-full w-10 h-10" />
<div className="flex flex-col gap-1 items-start">
<span>{displayUser.name}</span>
<span className="text-sm opacity-75">{displayUser.email}</span>
</div>
</div>
);
const UserDisplay = (displayUser: User) => (
<div className="flex w-full p-4 gap-4 items-center hover:bg-mti-purple-ultralight cursor-pointer transition ease-in-out duration-300">
<img src={displayUser.profilePicture} alt={displayUser.name} className="rounded-full w-10 h-10" />
<div className="flex flex-col gap-1 items-start">
<span>{displayUser.name}</span>
<span className="text-sm opacity-75">{displayUser.email}</span>
</div>
</div>
);
return (
<>
<Head>
<title>EnCoach</title>
<meta
name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<ToastContainer />
<Layout user={user}>
<div className="w-full flex flex-col gap-4">
{entities.length > 0 && (
<div className="w-fit self-end bg-neutral-200 px-2 rounded-lg py-1">
<b>{mapBy(entities, "label")?.join(", ")}</b>
</div>
)}
<section className="grid grid-cols-5 -md:grid-cols-2 place-items-center gap-4 text-center">
<IconCard
Icon={BsPersonFill}
onClick={() => router.push("/users?type=student")}
label="Students"
value={students.length}
color="purple"
/>
<IconCard
onClick={() => router.push("/classrooms")}
Icon={BsPeople}
label="Classrooms"
value={groups.length}
color="purple"
/>
<IconCard Icon={BsClipboard2Data} label="Exams Performed" value={uniqBy(stats, "exam").length} color="purple" />
<IconCard Icon={BsPaperclip} label="Average Level" value={averageLevelCalculator(stats).toFixed(1)} color="purple" />
<IconCard
Icon={BsEnvelopePaper}
onClick={() => router.push("/assignments")}
label="Assignments"
value={assignments.filter((a) => !a.archived).length}
color="purple"
/>
</section>
</div>
return (
<>
<Head>
<title>EnCoach</title>
<meta
name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<ToastContainer />
<Layout user={user}>
<div className="w-full flex flex-col gap-4">
{entities.length > 0 && (
<div className="w-fit self-end bg-neutral-200 px-2 rounded-lg py-1">
<b>{mapBy(entities, "label")?.join(", ")}</b>
</div>
)}
<section className="grid grid-cols-5 -md:grid-cols-2 place-items-center gap-4 text-center">
<IconCard
Icon={BsPersonFill}
onClick={() => router.push("/users?type=student")}
label="Students"
value={students.length}
color="purple"
/>
<IconCard
onClick={() => router.push("/classrooms")}
Icon={BsPeople}
label="Classrooms"
value={groups.length}
color="purple"
/>
<IconCard Icon={BsClipboard2Data} label="Exams Performed" value={uniqBy(stats, "exam").length} color="purple" />
<IconCard Icon={BsPaperclip} label="Average Level" value={averageLevelCalculator(stats).toFixed(1)} color="purple" />
<IconCard
Icon={BsEnvelopePaper}
onClick={() => router.push("/assignments")}
label="Assignments"
value={assignments.filter((a) => !a.archived).length}
color="purple"
/>
</section>
</div>
<section className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full justify-between">
<UserDisplayList
users={students.sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))}
title="Latest Students"
/>
<UserDisplayList
users={students.sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels))}
title="Highest level students"
/>
<UserDisplayList
users={
students
.sort(
(a, b) =>
Object.keys(groupByExam(filterBy(stats, "user", b))).length -
Object.keys(groupByExam(filterBy(stats, "user", a))).length,
)
}
title="Highest exam count students"
/>
</section>
</Layout>
</>
);
<section className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full justify-between">
<UserDisplayList
users={students.sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))}
title="Latest Students"
/>
<UserDisplayList
users={students.sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels))}
title="Highest level students"
/>
<UserDisplayList
users={
students
.sort(
(a, b) =>
Object.keys(groupByExam(filterBy(stats, "user", b))).length -
Object.keys(groupByExam(filterBy(stats, "user", a))).length,
)
}
title="Highest exam count students"
/>
</section>
</Layout>
</>
);
}

View File

@@ -4,34 +4,35 @@ import Layout from "@/components/High/Layout";
import Select from "@/components/Low/Select";
import Tooltip from "@/components/Low/Tooltip";
import { useEntityPermission } from "@/hooks/useEntityPermissions";
import {useListSearch} from "@/hooks/useListSearch";
import { useListSearch } from "@/hooks/useListSearch";
import usePagination from "@/hooks/usePagination";
import {Entity, EntityWithRoles, Role} from "@/interfaces/entity";
import {GroupWithUsers, User} from "@/interfaces/user";
import {sessionOptions} from "@/lib/session";
import {USER_TYPE_LABELS} from "@/resources/user";
import { Entity, EntityWithRoles, Role } from "@/interfaces/entity";
import { GroupWithUsers, User } from "@/interfaces/user";
import { sessionOptions } from "@/lib/session";
import { USER_TYPE_LABELS } from "@/resources/user";
import { findBy, mapBy, redirect, serialize } from "@/utils";
import {getEntityWithRoles} from "@/utils/entities.be";
import {convertToUsers, getGroup} from "@/utils/groups.be";
import {shouldRedirectHome} from "@/utils/navigation.disabled";
import {checkAccess, doesEntityAllow, getTypesOfUser} from "@/utils/permissions";
import {getUserName, isAdmin} from "@/utils/users";
import {filterAllowedUsers, getEntitiesUsers, getEntityUsers, getLinkedUsers, getSpecificUsers, getUsers} from "@/utils/users.be";
import { getEntityWithRoles } from "@/utils/entities.be";
import { convertToUsers, getGroup } from "@/utils/groups.be";
import { shouldRedirectHome } from "@/utils/navigation.disabled";
import { checkAccess, doesEntityAllow, getTypesOfUser } from "@/utils/permissions";
import { getUserName, isAdmin } from "@/utils/users";
import { filterAllowedUsers, getEntitiesUsers, getEntityUsers, getLinkedUsers, getSpecificUsers, getUsers } from "@/utils/users.be";
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
import axios from "axios";
import clsx from "clsx";
import {withIronSessionSsr} from "iron-session/next";
import { withIronSessionSsr } from "iron-session/next";
import moment from "moment";
import Head from "next/head";
import Link from "next/link";
import {useRouter} from "next/router";
import {Divider} from "primereact/divider";
import {useEffect, useMemo, useState} from "react";
import { useRouter } from "next/router";
import { Divider } from "primereact/divider";
import { useEffect, useMemo, useState } from "react";
import {
BsChevronLeft,
BsClockFill,
BsEnvelopeFill,
BsFillPersonVcardFill,
BsHash,
BsPerson,
BsPlus,
BsSquare,
@@ -40,15 +41,15 @@ import {
BsTrash,
BsX,
} from "react-icons/bs";
import {toast} from "react-toastify";
import { toast } from "react-toastify";
export const getServerSideProps = withIronSessionSsr(async ({req, params}) => {
export const getServerSideProps = withIronSessionSsr(async ({ req, params }) => {
const user = req.session.user as User;
if (!user) return redirect("/login")
if (shouldRedirectHome(user)) return redirect("/")
const {id} = params as {id: string};
const { id } = params as { id: string };
const entity = await getEntityWithRoles(id);
if (!entity) return redirect("/entities")
@@ -56,12 +57,12 @@ export const getServerSideProps = withIronSessionSsr(async ({req, params}) => {
if (!doesEntityAllow(user, entity, "view_entities")) return redirect(`/entities`)
const linkedUsers = await (isAdmin(user) ? getUsers() : getEntitiesUsers(mapBy(user.entities, 'id'),
{$and: [{type: {$ne: "developer"}}, {type: {$ne: "admin"}}]}))
{ $and: [{ type: { $ne: "developer" } }, { type: { $ne: "admin" } }] }))
const entityUsers = await (isAdmin(user) ? getEntityUsers(id) : filterAllowedUsers(user, [entity]));
const usersWithRole = entityUsers.map((u) => {
const e = u.entities.find((e) => e.id === id);
return {...u, role: findBy(entity.roles, 'id', e?.role)};
return { ...u, role: findBy(entity.roles, 'id', e?.role) };
});
return {
@@ -74,7 +75,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, params}) => {
};
}, sessionOptions);
type UserWithRole = User & {role?: Role};
type UserWithRole = User & { role?: Role };
interface Props {
user: User;
@@ -83,7 +84,7 @@ interface Props {
linkedUsers: User[];
}
export default function Home({user, entity, users, linkedUsers}: Props) {
export default function Home({ user, entity, users, linkedUsers }: Props) {
const [isAdding, setIsAdding] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
@@ -110,7 +111,7 @@ export default function Home({user, entity, users, linkedUsers}: Props) {
setIsLoading(true);
axios
.patch(`/api/entities/${entity.id}/users`, {add: false, members: selectedUsers})
.patch(`/api/entities/${entity.id}/users`, { add: false, members: selectedUsers })
.then(() => {
toast.success("The entity has been updated successfully!");
router.replace(router.asPath);
@@ -132,7 +133,7 @@ export default function Home({user, entity, users, linkedUsers}: Props) {
const defaultRole = findBy(entity.roles, 'isDefault', true)!
axios
.patch(`/api/entities/${entity.id}/users`, {add: true, members: selectedUsers, role: defaultRole.id})
.patch(`/api/entities/${entity.id}/users`, { add: true, members: selectedUsers, role: defaultRole.id })
.then(() => {
toast.success("The entity has been updated successfully!");
router.replace(router.asPath);
@@ -153,7 +154,28 @@ export default function Home({user, entity, users, linkedUsers}: Props) {
setIsLoading(true);
axios
.patch(`/api/entities/${entity.id}`, {label})
.patch(`/api/entities/${entity.id}`, { label })
.then(() => {
toast.success("The entity has been updated successfully!");
router.replace(router.asPath);
})
.catch((e) => {
console.error(e);
toast.error("Something went wrong!");
})
.finally(() => setIsLoading(false));
};
const editLicenses = () => {
if (!isAdmin(user)) return;
const licenses = prompt("Update the number of licenses:", (entity.licenses || 0).toString());
if (!licenses) return;
if (!parseInt(licenses) || parseInt(licenses) <= 0) return toast.error("Write a valid number of licenses!")
setIsLoading(true);
axios
.patch(`/api/entities/${entity.id}`, { licenses })
.then(() => {
toast.success("The entity has been updated successfully!");
router.replace(router.asPath);
@@ -190,7 +212,7 @@ export default function Home({user, entity, users, linkedUsers}: Props) {
setIsLoading(true);
axios
.post(`/api/roles/${role}/users`, {users: selectedUsers})
.post(`/api/roles/${role}/users`, { users: selectedUsers })
.then(() => {
toast.success("The role has been assigned successfully!");
router.replace(router.asPath);
@@ -274,7 +296,7 @@ export default function Home({user, entity, users, linkedUsers}: Props) {
className="text-mti-purple hover:text-mti-purple-dark transition ease-in-out duration-300 text-xl">
<BsChevronLeft />
</Link>
<h2 className="font-bold text-2xl">{entity.label}</h2>
<h2 className="font-bold text-2xl">{entity.label} {isAdmin(user) && `- ${entity.licenses || 0} licenses`}</h2>
</div>
</div>
<div className="flex items-center gap-2">
@@ -285,6 +307,15 @@ export default function Home({user, entity, users, linkedUsers}: Props) {
<BsTag />
<span className="text-xs">Rename Entity</span>
</button>
{isAdmin(user) && (
<button
onClick={editLicenses}
disabled={isLoading || !isAdmin(user)}
className="flex items-center gap-1 px-2 py-2 border rounded-full hover:bg-neutral-100 disabled:hover:bg-transparent disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition ease-in-out duration-300">
<BsHash />
<span className="text-xs">Edit Licenses</span>
</button>
)}
<button
onClick={() => router.push(`/entities/${entity.id}/roles`)}
disabled={isLoading || !canViewRoles}
@@ -325,7 +356,7 @@ export default function Home({user, entity, users, linkedUsers}: Props) {
{entity.roles.map((role) => (
<MenuItem key={role.id}>
<button onClick={() => assignUsersToRole(role.id)} className="p-4 hover:bg-neutral-100 w-32">
{ role.label }
{role.label}
</button>
</MenuItem>
))}

View File

@@ -3,32 +3,32 @@ import Layout from "@/components/High/Layout";
import Input from "@/components/Low/Input";
import Select from "@/components/Low/Select";
import Tooltip from "@/components/Low/Tooltip";
import {useListSearch} from "@/hooks/useListSearch";
import { useListSearch } from "@/hooks/useListSearch";
import usePagination from "@/hooks/usePagination";
import {Entity, EntityWithRoles} from "@/interfaces/entity";
import {User} from "@/interfaces/user";
import {sessionOptions} from "@/lib/session";
import {USER_TYPE_LABELS} from "@/resources/user";
import {mapBy, redirect, serialize} from "@/utils";
import {getEntities, getEntitiesWithRoles} from "@/utils/entities.be";
import {shouldRedirectHome} from "@/utils/navigation.disabled";
import {getUserName} from "@/utils/users";
import {getLinkedUsers, getUsers} from "@/utils/users.be";
import { Entity, EntityWithRoles } from "@/interfaces/entity";
import { User } from "@/interfaces/user";
import { sessionOptions } from "@/lib/session";
import { USER_TYPE_LABELS } from "@/resources/user";
import { mapBy, redirect, serialize } from "@/utils";
import { getEntities, getEntitiesWithRoles } from "@/utils/entities.be";
import { shouldRedirectHome } from "@/utils/navigation.disabled";
import { getUserName } from "@/utils/users";
import { getLinkedUsers, getUsers } from "@/utils/users.be";
import axios from "axios";
import clsx from "clsx";
import {withIronSessionSsr} from "iron-session/next";
import { withIronSessionSsr } from "iron-session/next";
import moment from "moment";
import Head from "next/head";
import Link from "next/link";
import {useRouter} from "next/router";
import {Divider} from "primereact/divider";
import {useState} from "react";
import {BsCheck, BsChevronLeft, BsClockFill, BsEnvelopeFill, BsStopwatchFill} from "react-icons/bs";
import {toast, ToastContainer} from "react-toastify";
import { useRouter } from "next/router";
import { Divider } from "primereact/divider";
import { useState } from "react";
import { BsCheck, BsChevronLeft, BsClockFill, BsEnvelopeFill, BsStopwatchFill } from "react-icons/bs";
import { toast, ToastContainer } from "react-toastify";
import { requestUser } from "@/utils/api";
import { findAllowedEntities } from "@/utils/permissions";
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res)
if (!user) return redirect("/login")
@@ -38,7 +38,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
const users = await getUsers()
return {
props: serialize({user, users: users.filter((x) => x.id !== user.id)}),
props: serialize({ user, users: users.filter((x) => x.id !== user.id) }),
};
}, sessionOptions);
@@ -47,13 +47,14 @@ interface Props {
users: User[];
}
export default function Home({user, users}: Props) {
export default function Home({ user, users }: Props) {
const [isLoading, setIsLoading] = useState(false);
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
const [label, setLabel] = useState("");
const [licenses, setLicenses] = useState(0);
const {rows, renderSearch} = useListSearch<User>([["name"], ["corporateInformation", "companyInformation", "name"]], users);
const {items, renderMinimal} = usePagination<User>(rows, 16);
const { rows, renderSearch } = useListSearch<User>([["name"], ["corporateInformation", "companyInformation", "name"]], users);
const { items, renderMinimal } = usePagination<User>(rows, 16);
const router = useRouter();
@@ -64,7 +65,7 @@ export default function Home({user, users}: Props) {
setIsLoading(true);
axios
.post<Entity>(`/api/entities`, {label, members: selectedUsers})
.post<Entity>(`/api/entities`, { label, licenses, members: selectedUsers })
.then((result) => {
toast.success("Your entity has been created successfully!");
router.replace(`/entities/${result.data.id}`);
@@ -104,7 +105,7 @@ export default function Home({user, users}: Props) {
<div className="flex items-center gap-4">
<button
onClick={createGroup}
disabled={!label.trim() || isLoading}
disabled={!label.trim() || licenses <= 0 || isLoading}
className="flex items-center gap-1 px-2 py-2 border rounded-full border-mti-green bg-mti-green-light text-white hover:bg-mti-green-dark disabled:hover:bg-mti-green-light disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition ease-in-out duration-300">
<BsCheck />
<span className="text-xs">Create Entity</span>
@@ -112,9 +113,16 @@ export default function Home({user, users}: Props) {
</div>
</div>
<Divider />
<div className="flex flex-col gap-4 w-full">
<span className="font-semibold text-xl">Entity Label:</span>
<Input name="name" onChange={setLabel} type="text" placeholder="Entity A" />
<div className="w-full grid grid-cols-2 gap-4">
<div className="flex flex-col gap-4 w-full">
<span className="font-semibold text-xl">Entity Label:</span>
<Input name="name" onChange={setLabel} type="text" placeholder="Entity A" />
</div>
<div className="flex flex-col gap-4 w-full">
<span className="font-semibold text-xl">Licenses:</span>
<Input name="licenses" min={0} onChange={(v) => setLicenses(parseInt(v))} type="number" placeholder="12" />
</div>
</div>
<Divider />
<div className="flex items-center justify-between mb-4">

View File

@@ -6,7 +6,7 @@ import { ToastContainer } from "react-toastify";
import Layout from "@/components/High/Layout";
import { GroupWithUsers, User } from "@/interfaces/user";
import { shouldRedirectHome } from "@/utils/navigation.disabled";
import { getUserName } from "@/utils/users";
import { getUserName, isAdmin } from "@/utils/users";
import { convertToUsers, getGroupsForUser } from "@/utils/groups.be";
import { countEntityUsers, getEntityUsers, getSpecificUsers, getUsers } from "@/utils/users.be";
import { checkAccess, findAllowedEntities, getTypesOfUser } from "@/utils/permissions";
@@ -64,7 +64,7 @@ export default function Home({ user, entities }: Props) {
</span>
<span className="flex items-center gap-1">
<span className="bg-mti-purple text-white font-semibold px-2">Members</span>
<span className="bg-mti-purple-light/50 px-2">{count}</span>
<span className="bg-mti-purple-light/50 px-2">{count}{isAdmin(user) && ` / ${entity.licenses || 0}`}</span>
</span>
<span>
{users.map(getUserName).join(", ")}{' '}

View File

@@ -1,20 +1,20 @@
/* eslint-disable @next/next/no-img-element */
import Head from "next/head";
import {withIronSessionSsr} from "iron-session/next";
import {sessionOptions} from "@/lib/session";
import { withIronSessionSsr } from "iron-session/next";
import { sessionOptions } from "@/lib/session";
import useUser from "@/hooks/useUser";
import {toast, ToastContainer} from "react-toastify";
import { toast, ToastContainer } from "react-toastify";
import Layout from "@/components/High/Layout";
import {shouldRedirectHome} from "@/utils/navigation.disabled";
import { shouldRedirectHome } from "@/utils/navigation.disabled";
import usePayments from "@/hooks/usePayments";
import usePaypalPayments from "@/hooks/usePaypalPayments";
import {Payment, PaypalPayment} from "@/interfaces/paypal";
import {CellContext, createColumnHelper, flexRender, getCoreRowModel, HeaderGroup, Table, useReactTable} from "@tanstack/react-table";
import {CURRENCIES} from "@/resources/paypal";
import {BsTrash} from "react-icons/bs";
import { Payment, PaypalPayment } from "@/interfaces/paypal";
import { CellContext, createColumnHelper, flexRender, getCoreRowModel, HeaderGroup, Table, useReactTable } from "@tanstack/react-table";
import { CURRENCIES } from "@/resources/paypal";
import { BsTrash } from "react-icons/bs";
import axios from "axios";
import {useEffect, useState, useMemo} from "react";
import {AgentUser, CorporateUser, User} from "@/interfaces/user";
import { useEffect, useState, useMemo } from "react";
import { AgentUser, CorporateUser, User } from "@/interfaces/user";
import UserCard from "@/components/UserCard";
import Modal from "@/components/Modal";
import clsx from "clsx";
@@ -26,15 +26,15 @@ import Input from "@/components/Low/Input";
import ReactDatePicker from "react-datepicker";
import moment from "moment";
import PaymentAssetManager from "@/components/PaymentAssetManager";
import {toFixedNumber} from "@/utils/number";
import {CSVLink} from "react-csv";
import {Tab} from "@headlessui/react";
import {useListSearch} from "@/hooks/useListSearch";
import {checkAccess, getTypesOfUser} from "@/utils/permissions";
import { toFixedNumber } from "@/utils/number";
import { CSVLink } from "react-csv";
import { Tab } from "@headlessui/react";
import { useListSearch } from "@/hooks/useListSearch";
import { checkAccess, getTypesOfUser } from "@/utils/permissions";
import { requestUser } from "@/utils/api";
import { redirect } from "@/utils";
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res)
if (!user) return redirect("/login")
@@ -43,18 +43,18 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
}
return {
props: {user},
props: { user },
};
}, sessionOptions);
const columnHelper = createColumnHelper<Payment>();
const paypalColumnHelper = createColumnHelper<PaypalPaymentWithUserData>();
const PaymentCreator = ({onClose, reload, showComission = false}: {onClose: () => void; reload: () => void; showComission: boolean}) => {
const PaymentCreator = ({ onClose, reload, showComission = false }: { onClose: () => void; reload: () => void; showComission: boolean }) => {
const [corporate, setCorporate] = useState<CorporateUser>();
const [date, setDate] = useState<Date>(new Date());
const {users} = useUsers();
const { users } = useUsers();
const price = corporate?.corporateInformation?.payment?.value || 0;
const commission = corporate?.corporateInformation?.payment?.commission || 0;
@@ -101,13 +101,13 @@ const PaymentCreator = ({onClose, reload, showComission = false}: {onClose: () =
options={(users.filter((u) => u.type === "corporate") as CorporateUser[]).map((user) => ({
value: user.id,
meta: user,
label: `${user.corporateInformation?.companyInformation?.name || user.name} - ${user.email}`,
label: `${user.name} - ${user.email}`,
}))}
defaultValue={{value: "undefined", label: "Select an account"}}
defaultValue={{ value: "undefined", label: "Select an account" }}
onChange={(value) => setCorporate((value as any)?.meta ?? undefined)}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({...base, zIndex: 9999}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
control: (styles) => ({
...styles,
paddingLeft: "4px",
@@ -129,10 +129,10 @@ const PaymentCreator = ({onClose, reload, showComission = false}: {onClose: () =
<div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">Price *</label>
<div className="w-full grid grid-cols-5 gap-2">
<Input name="paymentValue" onChange={() => {}} type="number" value={price} defaultValue={0} className="col-span-3" disabled />
<Input name="paymentValue" onChange={() => { }} type="number" value={price} defaultValue={0} className="col-span-3" disabled />
<Select
className="px-4 col-span-2 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool bg-mti-gray-platinum/40 text-mti-gray-dim cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none"
options={CURRENCIES.map(({label, currency}) => ({
options={CURRENCIES.map(({ label, currency }) => ({
value: currency,
label,
}))}
@@ -140,14 +140,14 @@ const PaymentCreator = ({onClose, reload, showComission = false}: {onClose: () =
value: currency || "EUR",
label: CURRENCIES.find((c) => c.currency === currency)?.label || "Euro",
}}
onChange={() => {}}
onChange={() => { }}
value={{
value: currency || "EUR",
label: CURRENCIES.find((c) => c.currency === currency)?.label || "Euro",
}}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({...base, zIndex: 9999}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
control: (styles) => ({
...styles,
paddingLeft: "4px",
@@ -171,7 +171,7 @@ const PaymentCreator = ({onClose, reload, showComission = false}: {onClose: () =
<div className="flex gap-4 w-full">
<div className="flex flex-col w-full gap-3">
<label className="font-normal text-base text-mti-gray-dim">Commission *</label>
<Input name="commission" onChange={() => {}} type="number" defaultValue={0} value={commission} disabled />
<Input name="commission" onChange={() => { }} type="number" defaultValue={0} value={commission} disabled />
</div>
<div className="flex flex-col w-full gap-3">
<label className="font-normal text-base text-mti-gray-dim">Commission Value*</label>
@@ -277,16 +277,16 @@ export default function PaymentRecord() {
const [selectedCorporateUser, setSelectedCorporateUser] = useState<User>();
const [selectedAgentUser, setSelectedAgentUser] = useState<User>();
const [isCreatingPayment, setIsCreatingPayment] = useState(false);
const [filters, setFilters] = useState<{filter: (p: Payment) => boolean; id: string}[]>([]);
const [filters, setFilters] = useState<{ filter: (p: Payment) => boolean; id: string }[]>([]);
const [displayPayments, setDisplayPayments] = useState<Payment[]>([]);
const [corporate, setCorporate] = useState<User>();
const [agent, setAgent] = useState<User>();
const {user} = useUser({redirectTo: "/login"});
const {users, reload: reloadUsers} = useUsers();
const {payments: originalPayments, reload: reloadPayment} = usePayments();
const {payments: paypalPayments, reload: reloadPaypalPayment} = usePaypalPayments();
const { user } = useUser({ redirectTo: "/login" });
const { users, reload: reloadUsers } = useUsers();
const { payments: originalPayments, reload: reloadPayment } = usePayments();
const { payments: paypalPayments, reload: reloadPaypalPayment } = usePaypalPayments();
const [startDate, setStartDate] = useState<Date | null>(moment("01/01/2023").toDate());
const [endDate, setEndDate] = useState<Date | null>(moment().endOf("day").toDate());
@@ -331,11 +331,11 @@ export default function PaymentRecord() {
...(!agent
? []
: [
{
id: "agent-filter",
filter: (p: Payment) => p.agent === agent.id,
},
]),
{
id: "agent-filter",
filter: (p: Payment) => p.agent === agent.id,
},
]),
]);
}, [agent]);
@@ -345,18 +345,18 @@ export default function PaymentRecord() {
...(!corporate
? []
: [
{
id: "corporate-filter",
filter: (p: Payment) => p.corporate === corporate.id,
},
]),
{
id: "corporate-filter",
filter: (p: Payment) => p.corporate === corporate.id,
},
]),
]);
}, [corporate]);
useEffect(() => {
setFilters((prev) => [
...prev.filter((x) => x.id !== "paid"),
...(typeof paid !== "boolean" ? [] : [{id: "paid", filter: (p: Payment) => p.isPaid === paid}]),
...(typeof paid !== "boolean" ? [] : [{ id: "paid", filter: (p: Payment) => p.isPaid === paid }]),
]);
}, [paid]);
@@ -366,11 +366,11 @@ export default function PaymentRecord() {
...(typeof commissionTransfer !== "boolean"
? []
: [
{
id: "commissionTransfer",
filter: (p: Payment) => !p.commissionTransfer === commissionTransfer,
},
]),
{
id: "commissionTransfer",
filter: (p: Payment) => !p.commissionTransfer === commissionTransfer,
},
]),
]);
}, [commissionTransfer]);
@@ -380,11 +380,11 @@ export default function PaymentRecord() {
...(typeof corporateTransfer !== "boolean"
? []
: [
{
id: "corporateTransfer",
filter: (p: Payment) => !p.corporateTransfer === corporateTransfer,
},
]),
{
id: "corporateTransfer",
filter: (p: Payment) => !p.corporateTransfer === corporateTransfer,
},
]),
]);
}, [corporateTransfer]);
@@ -395,7 +395,7 @@ export default function PaymentRecord() {
const updatePayment = (payment: Payment, key: string, value: any) => {
axios
.patch(`api/payments/${payment.id}`, {...payment, [key]: value})
.patch(`api/payments/${payment.id}`, { ...payment, [key]: value })
.then(() => toast.success("Updated the payment"))
.finally(reload);
};
@@ -540,7 +540,7 @@ export default function PaymentRecord() {
switch (key) {
case "agentCommission": {
const value = info.getValue();
return {value: `${value}%`};
return { value: `${value}%` };
}
case "agent": {
const user = users.find((x) => x.id === info.row.original.agent) as AgentUser;
@@ -553,18 +553,18 @@ export default function PaymentRecord() {
case "amount": {
const value = info.getValue();
const numberValue = toFixedNumber(value, 2);
return {value: numberValue};
return { value: numberValue };
}
case "date": {
const value = info.getValue();
return {value: moment(value).format("DD/MM/YYYY")};
return { value: moment(value).format("DD/MM/YYYY") };
}
case "corporate": {
const specificValue = info.row.original.corporate;
const user = users.find((x) => x.id === specificValue) as CorporateUser;
return {
user,
value: user?.corporateInformation.companyInformation.name || user?.name,
value: user?.name,
};
}
case "currency": {
@@ -576,7 +576,7 @@ export default function PaymentRecord() {
case "corporateId":
default: {
const value = info.getValue();
return {value};
return { value };
}
}
};
@@ -588,7 +588,7 @@ export default function PaymentRecord() {
header: "Country Manager",
id: "agent",
cell: (info) => {
const {user, value} = columHelperValue(info.column.id, info);
const { user, value } = columHelperValue(info.column.id, info);
return (
<div
className={clsx(
@@ -604,7 +604,7 @@ export default function PaymentRecord() {
header: "Commission",
id: "agentCommission",
cell: (info) => {
const {value} = columHelperValue(info.column.id, info);
const { value } = columHelperValue(info.column.id, info);
return <>{value}</>;
},
}),
@@ -612,7 +612,7 @@ export default function PaymentRecord() {
header: "Commission Value",
id: "agentValue",
cell: (info) => {
const {value} = columHelperValue(info.column.id, info);
const { value } = columHelperValue(info.column.id, info);
const finalValue = `${value} ${info.row.original.currency}`;
return <span>{finalValue}</span>;
},
@@ -626,7 +626,7 @@ export default function PaymentRecord() {
header: "Corporate ID",
id: "corporateId",
cell: (info) => {
const {value} = columHelperValue(info.column.id, info);
const { value } = columHelperValue(info.column.id, info);
return value;
},
}),
@@ -634,7 +634,7 @@ export default function PaymentRecord() {
header: "Corporate",
id: "corporate",
cell: (info) => {
const {user, value} = columHelperValue(info.column.id, info);
const { user, value } = columHelperValue(info.column.id, info);
return (
<div
className={clsx(
@@ -650,7 +650,7 @@ export default function PaymentRecord() {
header: "Date",
id: "date",
cell: (info) => {
const {value} = columHelperValue(info.column.id, info);
const { value } = columHelperValue(info.column.id, info);
return <span>{value}</span>;
},
}),
@@ -658,7 +658,7 @@ export default function PaymentRecord() {
header: "Amount",
id: "amount",
cell: (info) => {
const {value} = columHelperValue(info.column.id, info);
const { value } = columHelperValue(info.column.id, info);
const currency = CURRENCIES.find((x) => x.currency === info.row.original.currency)?.label;
const finalValue = `${value} ${currency}`;
return <span>{finalValue}</span>;
@@ -669,7 +669,7 @@ export default function PaymentRecord() {
header: "Paid",
id: "isPaid",
cell: (info) => {
const {value} = columHelperValue(info.column.id, info);
const { value } = columHelperValue(info.column.id, info);
return (
<Checkbox
@@ -691,7 +691,7 @@ export default function PaymentRecord() {
{
header: "",
id: "actions",
cell: ({row}: {row: {original: Payment}}) => {
cell: ({ row }: { row: { original: Payment } }) => {
return (
<div className="flex gap-4">
{user?.type !== "agent" && (
@@ -720,7 +720,7 @@ export default function PaymentRecord() {
})
.map((p) => {
const user = users.find((x) => x.id === p.userId) as User;
return {...p, name: user?.name, email: user?.email};
return { ...p, name: user?.name, email: user?.email };
}),
[paypalPayments, users, startDatePaymob, endDatePaymob],
);
@@ -730,7 +730,7 @@ export default function PaymentRecord() {
header: "Order ID",
id: "orderId",
cell: (info) => {
const {value} = columHelperValue(info.column.id, info);
const { value } = columHelperValue(info.column.id, info);
return <span>{value}</span>;
},
}),
@@ -738,7 +738,7 @@ export default function PaymentRecord() {
header: "Status",
id: "status",
cell: (info) => {
const {value} = columHelperValue(info.column.id, info);
const { value } = columHelperValue(info.column.id, info);
return <span>{value}</span>;
},
}),
@@ -746,7 +746,7 @@ export default function PaymentRecord() {
header: "User Name",
id: "name",
cell: (info) => {
const {value} = columHelperValue(info.column.id, info);
const { value } = columHelperValue(info.column.id, info);
return <span>{value}</span>;
},
}),
@@ -754,7 +754,7 @@ export default function PaymentRecord() {
header: "Email",
id: "email",
cell: (info) => {
const {value} = columHelperValue(info.column.id, info);
const { value } = columHelperValue(info.column.id, info);
return <span>{value}</span>;
},
}),
@@ -762,7 +762,7 @@ export default function PaymentRecord() {
header: "Amount",
id: "value",
cell: (info) => {
const {value} = columHelperValue(info.column.id, info);
const { value } = columHelperValue(info.column.id, info);
const finalValue = `${value} ${info.row.original.currency}`;
return <span>{finalValue}</span>;
},
@@ -771,7 +771,7 @@ export default function PaymentRecord() {
header: "Date",
id: "createdAt",
cell: (info) => {
const {value} = columHelperValue(info.column.id, info);
const { value } = columHelperValue(info.column.id, info);
return <span>{moment(value).format("DD/MM/YYYY")}</span>;
},
}),
@@ -779,13 +779,13 @@ export default function PaymentRecord() {
header: "Expiration Date",
id: "subscriptionExpirationDate",
cell: (info) => {
const {value} = columHelperValue(info.column.id, info);
const { value } = columHelperValue(info.column.id, info);
return <span>{moment(value).format("DD/MM/YYYY")}</span>;
},
}),
];
const {rows: filteredRows, renderSearch} = useListSearch(paypalFilterRows, updatedPaypalPayments);
const { rows: filteredRows, renderSearch } = useListSearch(paypalFilterRows, updatedPaypalPayments);
const paypalTable = useReactTable({
data: filteredRows.sort((a, b) => moment(b.createdAt).diff(moment(a.createdAt), "second")),
@@ -809,7 +809,7 @@ export default function PaymentRecord() {
}}
user={selectedCorporateUser}
disabled
disabledFields={{countryManager: true}}
disabledFields={{ countryManager: true }}
/>
</div>
)}
@@ -859,7 +859,7 @@ export default function PaymentRecord() {
return [...accm, ...data];
}, []);
const {rows} = currentTable.getRowModel();
const { rows } = currentTable.getRowModel();
const finalColumns = [
...columns,
@@ -872,8 +872,8 @@ export default function PaymentRecord() {
return {
columns: finalColumns,
rows: rows.map((row) => {
return finalColumns.reduce((accm, {key}) => {
const {value} = columHelperValue(key, {
return finalColumns.reduce((accm, { key }) => {
const { value } = columHelperValue(key, {
row,
getValue: () => row.getValue(key),
});
@@ -886,7 +886,7 @@ export default function PaymentRecord() {
};
};
const {rows: csvRows, columns: csvColumns} = getCSVData();
const { rows: csvRows, columns: csvColumns } = getCSVData();
const renderTable = (table: Table<any>) => (
<table className="rounded-xl h-full bg-mti-purple-ultralight/40 w-full">
@@ -958,7 +958,7 @@ export default function PaymentRecord() {
<Tab.Group selectedIndex={selectedIndex} onChange={setSelectedIndex}>
<Tab.List className="flex space-x-1 rounded-xl bg-mti-purple-ultralight/40 p-1">
<Tab
className={({selected}) =>
className={({ selected }) =>
clsx(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-mti-purple-light",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-mti-purple-light focus:outline-none focus:ring-2",
@@ -970,7 +970,7 @@ export default function PaymentRecord() {
</Tab>
{checkAccess(user, ["developer", "admin"]) && (
<Tab
className={({selected}) =>
className={({ selected }) =>
clsx(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-mti-purple-light",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-mti-purple-light focus:outline-none focus:ring-2",
@@ -996,24 +996,22 @@ export default function PaymentRecord() {
options={(users.filter((u) => u.type === "corporate") as CorporateUser[]).map((user) => ({
value: user.id,
meta: user,
label: `${user.corporateInformation?.companyInformation?.name || user.name} - ${user.email}`,
label: `${user.name} - ${user.email}`,
}))}
defaultValue={
user.type === "corporate"
? {
value: user.id,
meta: user,
label: `${user.corporateInformation?.companyInformation?.name || user.name} - ${
user.email
}`,
}
value: user.id,
meta: user,
label: `${user.name} - ${user.email}`,
}
: undefined
}
isDisabled={user.type === "corporate"}
onChange={(value) => setCorporate((value as any)?.meta ?? undefined)}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({...base, zIndex: 9999}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
control: (styles) => ({
...styles,
paddingLeft: "4px",
@@ -1049,15 +1047,15 @@ export default function PaymentRecord() {
value={
agent
? {
value: agent?.id,
label: `${agent.name} - ${agent.email}`,
}
value: agent?.id,
label: `${agent.name} - ${agent.email}`,
}
: undefined
}
onChange={(value) => setAgent(value !== null ? (value as any).meta : undefined)}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({...base, zIndex: 9999}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
control: (styles) => ({
...styles,
paddingLeft: "4px",
@@ -1092,7 +1090,7 @@ export default function PaymentRecord() {
}}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({...base, zIndex: 9999}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
control: (styles) => ({
...styles,
paddingLeft: "4px",
@@ -1149,7 +1147,7 @@ export default function PaymentRecord() {
}}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({...base, zIndex: 9999}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
control: (styles) => ({
...styles,
paddingLeft: "4px",
@@ -1183,7 +1181,7 @@ export default function PaymentRecord() {
}}
menuPortalTarget={document?.body}
styles={{
menuPortal: (base) => ({...base, zIndex: 9999}),
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
control: (styles) => ({
...styles,
paddingLeft: "4px",

View File

@@ -1,16 +1,16 @@
/* eslint-disable @next/next/no-img-element */
import Head from "next/head";
import {withIronSessionSsr} from "iron-session/next";
import {sessionOptions} from "@/lib/session";
import {ChangeEvent, Dispatch, ReactNode, SetStateAction, useEffect, useRef, useState} from "react";
import { withIronSessionSsr } from "iron-session/next";
import { sessionOptions } from "@/lib/session";
import { ChangeEvent, Dispatch, ReactNode, SetStateAction, useEffect, useRef, useState } from "react";
import useUser from "@/hooks/useUser";
import {toast, ToastContainer} from "react-toastify";
import { toast, ToastContainer } from "react-toastify";
import Layout from "@/components/High/Layout";
import Input from "@/components/Low/Input";
import Button from "@/components/Low/Button";
import Link from "next/link";
import axios from "axios";
import {ErrorMessage} from "@/constants/errors";
import { ErrorMessage } from "@/constants/errors";
import clsx from "clsx";
import {
CorporateUser,
@@ -23,32 +23,32 @@ import {
Group,
} from "@/interfaces/user";
import CountrySelect from "@/components/Low/CountrySelect";
import {shouldRedirectHome} from "@/utils/navigation.disabled";
import { shouldRedirectHome } from "@/utils/navigation.disabled";
import moment from "moment";
import {BsCamera, BsQuestionCircleFill} from "react-icons/bs";
import {USER_TYPE_LABELS} from "@/resources/user";
import { BsCamera, BsQuestionCircleFill } from "react-icons/bs";
import { USER_TYPE_LABELS } from "@/resources/user";
import useGroups from "@/hooks/useGroups";
import useUsers from "@/hooks/useUsers";
import {convertBase64, redirect} from "@/utils";
import {Divider} from "primereact/divider";
import { convertBase64, redirect } from "@/utils";
import { Divider } from "primereact/divider";
import GenderInput from "@/components/High/GenderInput";
import EmploymentStatusInput from "@/components/High/EmploymentStatusInput";
import TimezoneSelect from "@/components/Low/TImezoneSelect";
import Modal from "@/components/Modal";
import {Module} from "@/interfaces";
import { Module } from "@/interfaces";
import ModuleLevelSelector from "@/components/Medium/ModuleLevelSelector";
import Select from "@/components/Low/Select";
import {InstructorGender} from "@/interfaces/exam";
import {capitalize} from "lodash";
import { InstructorGender } from "@/interfaces/exam";
import { capitalize } from "lodash";
import TopicModal from "@/components/Medium/TopicModal";
import {v4} from "uuid";
import {checkAccess, getTypesOfUser} from "@/utils/permissions";
import {getParticipantGroups, getUserCorporate} from "@/utils/groups.be";
import {InferGetServerSidePropsType} from "next";
import {getUsers} from "@/utils/users.be";
import { v4 } from "uuid";
import { checkAccess, getTypesOfUser } from "@/utils/permissions";
import { getParticipantGroups, getUserCorporate } from "@/utils/groups.be";
import { InferGetServerSidePropsType } from "next";
import { getUsers } from "@/utils/users.be";
import { requestUser } from "@/utils/api";
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res)
if (!user) return redirect("/login")
@@ -72,9 +72,9 @@ interface Props {
linkedCorporate?: CorporateUser | MasterCorporateUser;
}
const DoubleColumnRow = ({children}: {children: ReactNode}) => <div className="flex flex-col lg:flex-row gap-8 w-full">{children}</div>;
const DoubleColumnRow = ({ children }: { children: ReactNode }) => <div className="flex flex-col lg:flex-row gap-8 w-full">{children}</div>;
function UserProfile({user, mutateUser, linkedCorporate, groups, users}: Props) {
function UserProfile({ user, mutateUser, linkedCorporate, groups, users }: Props) {
const [bio, setBio] = useState(user.bio || "");
const [name, setName] = useState(user.name || "");
const [email, setEmail] = useState(user.email || "");
@@ -182,21 +182,21 @@ function UserProfile({user, mutateUser, linkedCorporate, groups, users}: Props)
passport_id,
timezone,
},
...(user.type === "corporate" ? {corporateInformation} : {}),
...(user.type === "corporate" ? { corporateInformation } : {}),
...(user.type === "agent"
? {
agentInformation: {
companyName,
commercialRegistration,
arabName,
},
}
agentInformation: {
companyName,
commercialRegistration,
arabName,
},
}
: {}),
})
.then((response) => {
if (response.status === 200) {
toast.success("Your profile has been updated!");
mutateUser((response.data as {user: User}).user);
mutateUser((response.data as { user: User }).user);
setIsLoading(false);
return;
}
@@ -247,7 +247,7 @@ function UserProfile({user, mutateUser, linkedCorporate, groups, users}: Props)
<h1 className="text-4xl font-bold mb-6 -md:hidden">Edit Profile</h1>
<form className="flex flex-col items-center gap-6 w-full" onSubmit={(e) => e.preventDefault()}>
<DoubleColumnRow>
{user.type !== "corporate" && user.type !== "mastercorporate" ? (
{user.type !== "corporate" && user.type !== "mastercorporate" && (
<Input
label={user.type === "agent" ? "English name" : "Name"}
type="text"
@@ -257,25 +257,6 @@ function UserProfile({user, mutateUser, linkedCorporate, groups, users}: Props)
defaultValue={name}
required
/>
) : (
<Input
label="Company name"
type="text"
name="name"
disabled={!!linkedCorporate}
onChange={(e) =>
setCorporateInformation((prev) => ({
...prev!,
companyInformation: {
...prev!.companyInformation,
name: e,
},
}))
}
placeholder="Enter your company's name"
defaultValue={corporateInformation?.companyInformation?.name}
required
/>
)}
{user.type === "agent" && (
@@ -381,7 +362,7 @@ function UserProfile({user, mutateUser, linkedCorporate, groups, users}: Props)
<label className="font-normal text-base text-mti-gray-dim">Desired Levels</label>
<ModuleLevelSelector
levels={desiredLevels}
setLevels={setDesiredLevels as Dispatch<SetStateAction<{[key in Module]: number}>>}
setLevels={setDesiredLevels as Dispatch<SetStateAction<{ [key in Module]: number }>>}
/>
</div>
<div className="flex flex-col gap-3 w-full">
@@ -425,9 +406,9 @@ function UserProfile({user, mutateUser, linkedCorporate, groups, users}: Props)
}}
onChange={(value) => (value ? setPreferredGender(value.value as InstructorGender) : null)}
options={[
{value: "male", label: "Male"},
{value: "female", label: "Female"},
{value: "varied", label: "Varied"},
{ value: "male", label: "Male" },
{ value: "female", label: "Female" },
{ value: "varied", label: "Varied" },
]}
/>
</div>
@@ -461,15 +442,6 @@ function UserProfile({user, mutateUser, linkedCorporate, groups, users}: Props)
{user.type === "corporate" && (
<>
<DoubleColumnRow>
<Input
type="number"
name="companyUsers"
onChange={() => null}
label="Number of users"
defaultValue={user.corporateInformation?.companyInformation.userAmount}
disabled
required
/>
<Input
type="text"
name="pricing"
@@ -642,8 +614,8 @@ function UserProfile({user, mutateUser, linkedCorporate, groups, users}: Props)
);
}
export default function Home(props: {linkedCorporate?: CorporateUser | MasterCorporateUser; groups: Group[]; users: User[]}) {
const {user, mutateUser} = useUser({redirectTo: "/login"});
export default function Home(props: { linkedCorporate?: CorporateUser | MasterCorporateUser; groups: Group[]; users: User[] }) {
const { user, mutateUser } = useUser({ redirectTo: "/login" });
return (
<>

View File

@@ -16,7 +16,7 @@ import { checkAccess, getTypesOfUser } from "@/utils/permissions";
import usePermissions from "@/hooks/usePermissions";
import { useState } from "react";
import Modal from "@/components/Modal";
import IconCard from "@/dashboards/IconCard";
import IconCard from "@/components/IconCard";
import { BsCode, BsCodeSquare, BsGearFill, BsPeopleFill, BsPersonFill } from "react-icons/bs";
import UserCreator from "./(admin)/UserCreator";
import CorporateGradingSystem from "./(admin)/CorporateGradingSystem";

View File

@@ -1,199 +0,0 @@
/* eslint-disable @next/next/no-img-element */
import Head from "next/head";
import Navbar from "@/components/Navbar";
import { BsFileEarmarkText, BsPencil, BsStar, BsBook, BsHeadphones, BsPen, BsMegaphone } from "react-icons/bs";
import { withIronSessionSsr } from "iron-session/next";
import { sessionOptions } from "@/lib/session";
import { useEffect, useState } from "react";
import { averageScore, groupBySession, totalExams } from "@/utils/stats";
import useUser from "@/hooks/useUser";
import Diagnostic from "@/components/Diagnostic";
import { ToastContainer } from "react-toastify";
import { capitalize } from "lodash";
import { Module } from "@/interfaces";
import ProgressBar from "@/components/Low/ProgressBar";
import Layout from "@/components/High/Layout";
import { calculateAverageLevel } from "@/utils/score";
import axios from "axios";
import DemographicInformationInput from "@/components/DemographicInformationInput";
import moment from "moment";
import Link from "next/link";
import { MODULE_ARRAY } from "@/utils/moduleUtils";
import ProfileSummary from "@/components/ProfileSummary";
import StudentDashboard from "@/dashboards/Student";
import AdminDashboard from "@/dashboards/Admin";
import CorporateDashboard from "@/dashboards/Corporate";
import TeacherDashboard from "@/dashboards/Teacher";
import AgentDashboard from "@/dashboards/Agent";
import MasterCorporateDashboard from "@/dashboards/MasterCorporate";
import PaymentDue from "../(status)/PaymentDue";
import { useRouter } from "next/router";
import { PayPalScriptProvider } from "@paypal/react-paypal-js";
import { CorporateUser, MasterCorporateUser, Type, User, userTypes } from "@/interfaces/user";
import Select from "react-select";
import { USER_TYPE_LABELS } from "@/resources/user";
import { checkAccess, getTypesOfUser } from "@/utils/permissions";
import { getUserCorporate } from "@/utils/groups.be";
import { getUsers } from "@/utils/users.be";
import { requestUser } from "@/utils/api";
import { redirect, serialize } from "@/utils";
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res)
if (!user) return redirect("/login")
const linkedCorporate = (await getUserCorporate(user.id)) || null;
return {
props: serialize({ user, linkedCorporate }),
};
}, sessionOptions);
interface Props {
user: User;
linkedCorporate?: CorporateUser | MasterCorporateUser;
}
export default function Home({ user: propsUser, linkedCorporate }: Props) {
const [user, setUser] = useState(propsUser);
const [showDiagnostics, setShowDiagnostics] = useState(false);
const [showDemographicInput, setShowDemographicInput] = useState(false);
const [selectedScreen, setSelectedScreen] = useState<Type>("admin");
const { mutateUser } = useUser({ redirectTo: "/login" });
const router = useRouter();
useEffect(() => {
if (user) {
// setShowDemographicInput(!user.demographicInformation || !user.demographicInformation.country || !user.demographicInformation.phone);
setShowDiagnostics(user.isFirstLogin && user.type === "student");
}
}, [user]);
const checkIfUserExpired = () => {
const expirationDate = user!.subscriptionExpirationDate;
if (expirationDate === null || expirationDate === undefined) return false;
if (moment(expirationDate).isAfter(moment(new Date()))) return false;
return true;
};
if (user && (user.status === "paymentDue" || user.status === "disabled" || checkIfUserExpired())) {
return (
<>
<Head>
<title>EnCoach</title>
<meta
name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
{user.status === "disabled" && (
<Layout user={user} navDisabled>
<div className="flex flex-col items-center justify-center text-center w-full gap-4">
<span className="font-bold text-lg">Your account has been disabled!</span>
<span>Please contact an administrator if you believe this to be a mistake.</span>
</div>
</Layout>
)}
{(user.status === "paymentDue" || checkIfUserExpired()) && <PaymentDue hasExpired user={user} reload={router.reload} />}
</>
);
}
if (user && showDemographicInput) {
return (
<>
<Head>
<title>EnCoach</title>
<meta
name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Layout user={user} navDisabled>
<DemographicInformationInput
mutateUser={(user) => {
setUser(user);
mutateUser(user);
}}
user={user}
/>
</Layout>
</>
);
}
if (user && showDiagnostics) {
return (
<>
<Head>
<title>EnCoach</title>
<meta
name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<Layout user={user} navDisabled>
<Diagnostic user={user} onFinish={() => setShowDiagnostics(false)} />
</Layout>
</>
);
}
return (
<>
<Head>
<title>EnCoach</title>
<meta
name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<ToastContainer />
{user && (
<Layout user={user}>
{checkAccess(user, ["student"]) && <StudentDashboard linkedCorporate={linkedCorporate} user={user} />}
{checkAccess(user, ["teacher"]) && <TeacherDashboard linkedCorporate={linkedCorporate} user={user} />}
{checkAccess(user, ["corporate"]) && <CorporateDashboard linkedCorporate={linkedCorporate} user={user as CorporateUser} />}
{checkAccess(user, ["mastercorporate"]) && <MasterCorporateDashboard user={user as MasterCorporateUser} />}
{checkAccess(user, ["agent"]) && <AgentDashboard user={user} />}
{checkAccess(user, ["admin"]) && <AdminDashboard user={user} />}
{checkAccess(user, ["developer"]) && (
<>
<Select
options={userTypes.map((u) => ({
value: u,
label: USER_TYPE_LABELS[u],
}))}
value={{
value: selectedScreen,
label: USER_TYPE_LABELS[selectedScreen],
}}
onChange={(value) => (value ? setSelectedScreen(value.value) : setSelectedScreen("admin"))}
/>
{selectedScreen === "student" && <StudentDashboard linkedCorporate={linkedCorporate} user={user} />}
{selectedScreen === "teacher" && <TeacherDashboard linkedCorporate={linkedCorporate} user={user} />}
{selectedScreen === "corporate" && (
<CorporateDashboard linkedCorporate={linkedCorporate} user={user as unknown as CorporateUser} />
)}
{selectedScreen === "mastercorporate" && <MasterCorporateDashboard user={user as unknown as MasterCorporateUser} />}
{selectedScreen === "agent" && <AgentDashboard user={user} />}
{selectedScreen === "admin" && <AdminDashboard user={user} />}
</>
)}
</Layout>
)}
</>
);
}