Added more filters to the classroom
This commit is contained in:
@@ -52,8 +52,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res, params})
|
|||||||
const entity = await getEntityWithRoles(assignment.entity || "")
|
const entity = await getEntityWithRoles(assignment.entity || "")
|
||||||
if (!entity) return redirect("/assignments")
|
if (!entity) return redirect("/assignments")
|
||||||
|
|
||||||
if (!doesEntityAllow(user, entity, 'view_assignments') && !["admin", "developer"].includes(user.type))
|
if (!doesEntityAllow(user, entity, 'view_assignments')) return redirect("/assignments")
|
||||||
return redirect("/assignments")
|
|
||||||
|
|
||||||
const users = await (checkAccess(user, ["developer", "admin"]) ? getUsers() : getEntityUsers(entity.id));
|
const users = await (checkAccess(user, ["developer", "admin"]) ? getUsers() : getEntityUsers(entity.id));
|
||||||
|
|
||||||
@@ -296,6 +295,12 @@ export default function AssignmentView({user, users, entity, assignment}: Props)
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const copyLink = async () => {
|
||||||
|
const origin = window.location.origin
|
||||||
|
await navigator.clipboard.writeText(`${origin}/exam?assignment=${assignment.id}`)
|
||||||
|
toast.success("The URL to the assignment has been copied to your clipboard!")
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
@@ -388,6 +393,9 @@ export default function AssignmentView({user, users, entity, assignment}: Props)
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-4 w-full items-center justify-end">
|
<div className="flex gap-4 w-full items-center justify-end">
|
||||||
|
<Button variant="outline" color="purple" className="w-full max-w-[200px]" onClick={copyLink}>
|
||||||
|
Copy Link
|
||||||
|
</Button>
|
||||||
{assignment &&
|
{assignment &&
|
||||||
(assignment.results.length === assignment.assignees.length || moment().isAfter(moment(assignment.endDate))) && (
|
(assignment.results.length === assignment.assignees.length || moment().isAfter(moment(assignment.endDate))) && (
|
||||||
<Button variant="outline" color="red" className="w-full max-w-[200px]" onClick={deleteAssignment}>
|
<Button variant="outline" color="red" className="w-full max-w-[200px]" onClick={deleteAssignment}>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res, params})
|
|||||||
if (!assignment) return redirect("/assignments")
|
if (!assignment) return redirect("/assignments")
|
||||||
|
|
||||||
const entities = await (checkAccess(user, ["developer", "admin"]) ? getEntitiesWithRoles() : getEntitiesWithRoles(entityIDS));
|
const entities = await (checkAccess(user, ["developer", "admin"]) ? getEntitiesWithRoles() : getEntitiesWithRoles(entityIDS));
|
||||||
const entity = entities.find((e) => assignment.entity === assignment.entity)
|
const entity = entities.find((e) => e.id === assignment.entity)
|
||||||
|
|
||||||
if (!entity) return redirect("/assignments")
|
if (!entity) return redirect("/assignments")
|
||||||
if (!doesEntityAllow(user, entity, 'edit_assignment')) return redirect("/assignments")
|
if (!doesEntityAllow(user, entity, 'edit_assignment')) return redirect("/assignments")
|
||||||
@@ -197,6 +197,12 @@ export default function AssignmentsPage({assignment, user, users, entities, grou
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const copyLink = async () => {
|
||||||
|
const origin = window.location.origin
|
||||||
|
await navigator.clipboard.writeText(`${origin}/exam?assignment=${assignment.id}`)
|
||||||
|
toast.success("The URL to the assignment has been copied to your clipboard!")
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
@@ -558,6 +564,9 @@ export default function AssignmentsPage({assignment, user, users, entities, grou
|
|||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4 w-full justify-end">
|
<div className="flex gap-4 w-full justify-end">
|
||||||
|
<Button variant="outline" color="purple" className="w-full max-w-[200px]" onClick={copyLink}>
|
||||||
|
Copy Link
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="w-full max-w-[200px]"
|
className="w-full max-w-[200px]"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -96,9 +96,10 @@ export default function AssignmentsPage({user, users, groups, entities}: Props)
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const classrooms = useMemo(() => groups.filter((e) => e.entity === entity), [entity, groups]);
|
const classrooms = useMemo(() => groups.filter((e) => e.entity === entity), [entity, groups]);
|
||||||
|
const allowedUsers = useMemo(() => users.filter((u) => mapBy(u.entities, 'id').includes(entity || "")), [users, entity])
|
||||||
|
|
||||||
const userStudents = useMemo(() => users.filter((x) => x.type === "student"), [users]);
|
const userStudents = useMemo(() => allowedUsers.filter((x) => x.type === "student"), [allowedUsers]);
|
||||||
const userTeachers = useMemo(() => users.filter((x) => x.type === "teacher"), [users]);
|
const userTeachers = useMemo(() => allowedUsers.filter((x) => x.type === "teacher"), [allowedUsers]);
|
||||||
|
|
||||||
const {rows: filteredStudentsRows, renderSearch: renderStudentSearch} = useListSearch([["name"], ["email"]], userStudents);
|
const {rows: filteredStudentsRows, renderSearch: renderStudentSearch} = useListSearch([["name"], ["email"]], userStudents);
|
||||||
const {rows: filteredTeachersRows, renderSearch: renderTeacherSearch} = useListSearch([["name"], ["email"]], userTeachers);
|
const {rows: filteredTeachersRows, renderSearch: renderTeacherSearch} = useListSearch([["name"], ["email"]], userTeachers);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { EntityWithRoles } from "@/interfaces/entity";
|
|||||||
import {GroupWithUsers, User} from "@/interfaces/user";
|
import {GroupWithUsers, User} from "@/interfaces/user";
|
||||||
import {sessionOptions} from "@/lib/session";
|
import {sessionOptions} from "@/lib/session";
|
||||||
import {USER_TYPE_LABELS} from "@/resources/user";
|
import {USER_TYPE_LABELS} from "@/resources/user";
|
||||||
import { mapBy, redirect, serialize } from "@/utils";
|
import { filterBy, mapBy, redirect, serialize } from "@/utils";
|
||||||
import { requestUser } from "@/utils/api";
|
import { requestUser } from "@/utils/api";
|
||||||
import { getEntitiesWithRoles, getEntityWithRoles } from "@/utils/entities.be";
|
import { getEntitiesWithRoles, getEntityWithRoles } from "@/utils/entities.be";
|
||||||
import {convertToUsers, getGroup} from "@/utils/groups.be";
|
import {convertToUsers, getGroup} from "@/utils/groups.be";
|
||||||
@@ -19,6 +19,7 @@ import {getEntityUsers, getLinkedUsers, getSpecificUsers} from "@/utils/users.be
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import {withIronSessionSsr} from "iron-session/next";
|
import {withIronSessionSsr} from "iron-session/next";
|
||||||
|
import { capitalize } from "lodash";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -84,8 +85,6 @@ export default function Home({user, group, users, entity}: Props) {
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const allowGroupEdit = useMemo(() => checkAccess(user, ["admin", "developer", "mastercorporate"]) || user.id === group.admin.id, [user, group]);
|
|
||||||
|
|
||||||
const toggleUser = (u: User) => setSelectedUsers((prev) => (prev.includes(u.id) ? prev.filter((p) => p !== u.id) : [...prev, u.id]));
|
const toggleUser = (u: User) => setSelectedUsers((prev) => (prev.includes(u.id) ? prev.filter((p) => p !== u.id) : [...prev, u.id]));
|
||||||
|
|
||||||
const removeParticipants = () => {
|
const removeParticipants = () => {
|
||||||
@@ -263,6 +262,31 @@ export default function Home({user, group, users, entity}: Props) {
|
|||||||
{renderSearch()}
|
{renderSearch()}
|
||||||
{renderMinimal()}
|
{renderMinimal()}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-2 mt-4">
|
||||||
|
{['student', 'teacher', 'corporate'].map((type) => (
|
||||||
|
<button
|
||||||
|
key={type}
|
||||||
|
onClick={() => {
|
||||||
|
const typeUsers = mapBy(filterBy(isAdding ? nonParticipantUsers : group.participants, 'type', type), 'id')
|
||||||
|
if (typeUsers.every((u) => selectedUsers.includes(u))) {
|
||||||
|
setSelectedUsers((prev) => prev.filter((a) => !typeUsers.includes(a)));
|
||||||
|
} else {
|
||||||
|
setSelectedUsers((prev) => [...prev.filter((a) => !typeUsers.includes(a)), ...typeUsers]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={filterBy(isAdding ? nonParticipantUsers : group.participants, 'type', type).length === 0}
|
||||||
|
className={clsx(
|
||||||
|
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
|
||||||
|
"transition duration-300 ease-in-out",
|
||||||
|
"disabled:grayscale disabled:hover:bg-mti-purple-ultralight disabled:hover:text-mti-purple disabled:cursor-not-allowed",
|
||||||
|
filterBy(isAdding ? nonParticipantUsers : group.participants, 'type', type).length > 0 &&
|
||||||
|
filterBy(isAdding ? nonParticipantUsers : group.participants, 'type', type).every((u) => selectedUsers.includes(u.id)) &&
|
||||||
|
"!bg-mti-purple-light !text-white",
|
||||||
|
)}>
|
||||||
|
{capitalize(type)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="w-full h-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
<section className="w-full h-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {USER_TYPE_LABELS} from "@/resources/user";
|
|||||||
import {filterBy, mapBy, redirect, serialize} from "@/utils";
|
import {filterBy, mapBy, redirect, serialize} from "@/utils";
|
||||||
import {getEntities, getEntitiesWithRoles} from "@/utils/entities.be";
|
import {getEntities, getEntitiesWithRoles} from "@/utils/entities.be";
|
||||||
import {shouldRedirectHome} from "@/utils/navigation.disabled";
|
import {shouldRedirectHome} from "@/utils/navigation.disabled";
|
||||||
import {getUserName} from "@/utils/users";
|
import {getUserName, isAdmin} from "@/utils/users";
|
||||||
import {getEntitiesUsers, getLinkedUsers} from "@/utils/users.be";
|
import {getEntitiesUsers, getLinkedUsers} from "@/utils/users.be";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
@@ -22,11 +22,12 @@ import Head from "next/head";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import {useRouter} from "next/router";
|
import {useRouter} from "next/router";
|
||||||
import {Divider} from "primereact/divider";
|
import {Divider} from "primereact/divider";
|
||||||
import {useMemo, useState} from "react";
|
import {useEffect, useMemo, useState} from "react";
|
||||||
import {BsCheck, BsChevronLeft, BsClockFill, BsEnvelopeFill, BsStopwatchFill} from "react-icons/bs";
|
import {BsCheck, BsChevronLeft, BsClockFill, BsEnvelopeFill, BsStopwatchFill} from "react-icons/bs";
|
||||||
import {toast, ToastContainer} from "react-toastify";
|
import {toast, ToastContainer} from "react-toastify";
|
||||||
import { requestUser } from "@/utils/api";
|
import { requestUser } from "@/utils/api";
|
||||||
import { findAllowedEntities } from "@/utils/permissions";
|
import { findAllowedEntities } from "@/utils/permissions";
|
||||||
|
import { capitalize } from "lodash";
|
||||||
|
|
||||||
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
|
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
|
||||||
const user = await requestUser(req, res)
|
const user = await requestUser(req, res)
|
||||||
@@ -34,8 +35,8 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
|
|||||||
|
|
||||||
if (shouldRedirectHome(user)) return redirect("/")
|
if (shouldRedirectHome(user)) return redirect("/")
|
||||||
|
|
||||||
const users = await getEntitiesUsers(mapBy(user.entities, 'id'))
|
const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : mapBy(user.entities, "id"));
|
||||||
const entities = await getEntitiesWithRoles(mapBy(user.entities, "id"));
|
const users = await getEntitiesUsers(mapBy(entities, 'id'))
|
||||||
const allowedEntities = findAllowedEntities(user, entities, "create_classroom")
|
const allowedEntities = findAllowedEntities(user, entities, "create_classroom")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -57,11 +58,16 @@ export default function Home({user, users, entities}: Props) {
|
|||||||
|
|
||||||
const entityUsers = useMemo(() => !entity ? users : users.filter(u => mapBy(u.entities, 'id').includes(entity)), [entity, users])
|
const entityUsers = useMemo(() => !entity ? users : users.filter(u => mapBy(u.entities, 'id').includes(entity)), [entity, users])
|
||||||
|
|
||||||
const {rows, renderSearch} = useListSearch<User>([["name"], ["corporateInformation", "companyInformation", "name"]], entityUsers);
|
const {rows, renderSearch} = useListSearch<User>(
|
||||||
|
[["name"], ["type"], ["corporateInformation", "companyInformation", "name"]], entityUsers
|
||||||
|
);
|
||||||
|
|
||||||
const {items, renderMinimal} = usePagination<User>(rows, 16);
|
const {items, renderMinimal} = usePagination<User>(rows, 16);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffect(() => setSelectedUsers([]), [entity])
|
||||||
|
|
||||||
const createGroup = () => {
|
const createGroup = () => {
|
||||||
if (!name.trim()) return;
|
if (!name.trim()) return;
|
||||||
if (!entity) return;
|
if (!entity) return;
|
||||||
@@ -140,6 +146,31 @@ export default function Home({user, users, entities}: Props) {
|
|||||||
{renderSearch()}
|
{renderSearch()}
|
||||||
{renderMinimal()}
|
{renderMinimal()}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-2 mt-4">
|
||||||
|
{['student', 'teacher', 'corporate'].map((type) => (
|
||||||
|
<button
|
||||||
|
key={type}
|
||||||
|
onClick={() => {
|
||||||
|
const typeUsers = mapBy(filterBy(entityUsers, 'type', type), 'id')
|
||||||
|
if (typeUsers.every((u) => selectedUsers.includes(u))) {
|
||||||
|
setSelectedUsers((prev) => prev.filter((a) => !typeUsers.includes(a)));
|
||||||
|
} else {
|
||||||
|
setSelectedUsers((prev) => [...prev.filter((a) => !typeUsers.includes(a)), ...typeUsers]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={filterBy(entityUsers, 'type', type).length === 0}
|
||||||
|
className={clsx(
|
||||||
|
"bg-mti-purple-ultralight text-mti-purple px-4 py-2 rounded-full hover:text-white hover:bg-mti-purple-light",
|
||||||
|
"transition duration-300 ease-in-out",
|
||||||
|
"disabled:grayscale disabled:hover:bg-mti-purple-ultralight disabled:hover:text-mti-purple disabled:cursor-not-allowed",
|
||||||
|
filterBy(entityUsers, 'type', type).length > 0 &&
|
||||||
|
filterBy(entityUsers, 'type', type).every((u) => selectedUsers.includes(u.id)) &&
|
||||||
|
"!bg-mti-purple-light !text-white",
|
||||||
|
)}>
|
||||||
|
{capitalize(type)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="w-full h-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
<section className="w-full h-full grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {ToastContainer} from "react-toastify";
|
|||||||
import Layout from "@/components/High/Layout";
|
import Layout from "@/components/High/Layout";
|
||||||
import {GroupWithUsers, User} from "@/interfaces/user";
|
import {GroupWithUsers, User} from "@/interfaces/user";
|
||||||
import {shouldRedirectHome} from "@/utils/navigation.disabled";
|
import {shouldRedirectHome} from "@/utils/navigation.disabled";
|
||||||
import {getUserName} from "@/utils/users";
|
import {getUserName, isAdmin} from "@/utils/users";
|
||||||
import {convertToUsers, getGroupsForEntities} from "@/utils/groups.be";
|
import {convertToUsers, getGroupsForEntities} from "@/utils/groups.be";
|
||||||
import {getSpecificUsers} from "@/utils/users.be";
|
import {getSpecificUsers} from "@/utils/users.be";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
@@ -28,7 +28,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
|
|||||||
if (shouldRedirectHome(user)) return redirect("/")
|
if (shouldRedirectHome(user)) return redirect("/")
|
||||||
|
|
||||||
const entityIDS = mapBy(user.entities, "id");
|
const entityIDS = mapBy(user.entities, "id");
|
||||||
const entities = await getEntitiesWithRoles(entityIDS)
|
const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDS)
|
||||||
const allowedEntities = findAllowedEntities(user, entities, "view_classrooms")
|
const allowedEntities = findAllowedEntities(user, entities, "view_classrooms")
|
||||||
|
|
||||||
const groups = await getGroupsForEntities(mapBy(allowedEntities, 'id'));
|
const groups = await getGroupsForEntities(mapBy(allowedEntities, 'id'));
|
||||||
|
|||||||
@@ -44,3 +44,5 @@ export const getUserName = (user?: User) => {
|
|||||||
if (user.type === "corporate" || user.type === "mastercorporate") return user.corporateInformation?.companyInformation?.name || user.name;
|
if (user.type === "corporate" || user.type === "mastercorporate") return user.corporateInformation?.companyInformation?.name || user.name;
|
||||||
return user.name;
|
return user.name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isAdmin = (user: User) => ["admin", "developer"].includes(user.type)
|
||||||
|
|||||||
Reference in New Issue
Block a user