Cleared of the stuff the EnCoach team wanted changed
This commit is contained in:
@@ -43,7 +43,7 @@ export default function Diagnostic({onFinish}: Props) {
|
|||||||
if (exams.every((x) => !!x)) {
|
if (exams.every((x) => !!x)) {
|
||||||
setExams(exams.map((x) => x!));
|
setExams(exams.map((x) => x!));
|
||||||
setSelectedModules(exams.map((x) => x!.module));
|
setSelectedModules(exams.map((x) => x!.module));
|
||||||
router.push("/exercises");
|
router.push("/exam");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { EntityWithRoles } from "@/interfaces/entity";
|
||||||
import {User} from "@/interfaces/user";
|
import {User} from "@/interfaces/user";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import {useRouter} from "next/router";
|
import {useRouter} from "next/router";
|
||||||
@@ -7,6 +8,7 @@ import Sidebar from "../Sidebar";
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: User;
|
user: User;
|
||||||
|
entities?: EntityWithRoles[]
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
navDisabled?: boolean;
|
navDisabled?: boolean;
|
||||||
@@ -16,7 +18,17 @@ interface Props {
|
|||||||
onFocusLayerMouseEnter?: () => void;
|
onFocusLayerMouseEnter?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Layout({user, children, className, bgColor="bg-white", hideSidebar, navDisabled = false, focusMode = false, onFocusLayerMouseEnter}: Props) {
|
export default function Layout({
|
||||||
|
user,
|
||||||
|
entities = [],
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
bgColor="bg-white",
|
||||||
|
hideSidebar,
|
||||||
|
navDisabled = false,
|
||||||
|
focusMode = false,
|
||||||
|
onFocusLayerMouseEnter
|
||||||
|
}: Props) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -40,6 +52,7 @@ export default function Layout({user, children, className, bgColor="bg-white", h
|
|||||||
onFocusLayerMouseEnter={onFocusLayerMouseEnter}
|
onFocusLayerMouseEnter={onFocusLayerMouseEnter}
|
||||||
className="-md:hidden"
|
className="-md:hidden"
|
||||||
user={user}
|
user={user}
|
||||||
|
entities={entities}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ const StatsGridItem: React.FC<StatsGridItemProps> = ({
|
|||||||
.sort(sortByModule)
|
.sort(sortByModule)
|
||||||
.map((x) => x!.module),
|
.map((x) => x!.module),
|
||||||
);
|
);
|
||||||
router.push("/exercises");
|
router.push("/exam");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,16 +105,6 @@ export default function MobileMenu({
|
|||||||
>
|
>
|
||||||
Exams
|
Exams
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
|
||||||
href={disableNavigation ? "" : "/exercises"}
|
|
||||||
className={clsx(
|
|
||||||
"w-fit transition duration-300 ease-in-out",
|
|
||||||
path === "/exercises" &&
|
|
||||||
"text-mti-purple-light border-b-mti-purple-light border-b-2 font-semibold "
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
Exercises
|
|
||||||
</Link>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -12,24 +12,23 @@ import {
|
|||||||
BsCloudFill,
|
BsCloudFill,
|
||||||
BsCurrencyDollar,
|
BsCurrencyDollar,
|
||||||
BsClipboardData,
|
BsClipboardData,
|
||||||
BsFileLock,
|
|
||||||
BsPeople,
|
BsPeople,
|
||||||
} from "react-icons/bs";
|
} from "react-icons/bs";
|
||||||
import { CiDumbbell } from "react-icons/ci";
|
import { CiDumbbell } from "react-icons/ci";
|
||||||
import { RiLogoutBoxFill } from "react-icons/ri";
|
import { RiLogoutBoxFill } from "react-icons/ri";
|
||||||
import { SlPencil } from "react-icons/sl";
|
|
||||||
import { FaAward } from "react-icons/fa";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import FocusLayer from "@/components/FocusLayer";
|
import FocusLayer from "@/components/FocusLayer";
|
||||||
import { preventNavigation } from "@/utils/navigation.disabled";
|
import { preventNavigation } from "@/utils/navigation.disabled";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import usePreferencesStore from "@/stores/preferencesStore";
|
import usePreferencesStore from "@/stores/preferencesStore";
|
||||||
import { User } from "@/interfaces/user";
|
import { User } from "@/interfaces/user";
|
||||||
import useTicketsListener from "@/hooks/useTicketsListener";
|
import useTicketsListener from "@/hooks/useTicketsListener";
|
||||||
import { checkAccess, getTypesOfUser } from "@/utils/permissions";
|
import { checkAccess, getTypesOfUser } from "@/utils/permissions";
|
||||||
import usePermissions from "@/hooks/usePermissions";
|
import usePermissions from "@/hooks/usePermissions";
|
||||||
|
import { EntityWithRoles } from "@/interfaces/entity";
|
||||||
|
import { useAllowedEntitiesSomePermissions } from "@/hooks/useEntityPermissions";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
path: string;
|
path: string;
|
||||||
navDisabled?: boolean;
|
navDisabled?: boolean;
|
||||||
@@ -37,6 +36,7 @@ interface Props {
|
|||||||
onFocusLayerMouseEnter?: () => void;
|
onFocusLayerMouseEnter?: () => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
user: User;
|
user: User;
|
||||||
|
entities?: EntityWithRoles[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NavProps {
|
interface NavProps {
|
||||||
@@ -76,7 +76,15 @@ const Nav = ({ Icon, label, path, keyPath, disabled = false, isMinimized = false
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Sidebar({ path, navDisabled = false, focusMode = false, user, onFocusLayerMouseEnter, className }: Props) {
|
export default function Sidebar({
|
||||||
|
path,
|
||||||
|
entities = [],
|
||||||
|
navDisabled = false,
|
||||||
|
focusMode = false,
|
||||||
|
user,
|
||||||
|
onFocusLayerMouseEnter,
|
||||||
|
className
|
||||||
|
}: Props) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [isMinimized, toggleMinimize] = usePreferencesStore((state) => [state.isSidebarMinimized, state.toggleSidebarMinimized]);
|
const [isMinimized, toggleMinimize] = usePreferencesStore((state) => [state.isSidebarMinimized, state.toggleSidebarMinimized]);
|
||||||
@@ -84,6 +92,10 @@ export default function Sidebar({ path, navDisabled = false, focusMode = false,
|
|||||||
const { totalAssignedTickets } = useTicketsListener(user.id);
|
const { totalAssignedTickets } = useTicketsListener(user.id);
|
||||||
const { permissions } = usePermissions(user.id);
|
const { permissions } = usePermissions(user.id);
|
||||||
|
|
||||||
|
const entitiesAllowGeneration = useAllowedEntitiesSomePermissions(user, entities, [
|
||||||
|
"generate_reading", "generate_listening", "generate_writing", "generate_speaking", "generate_level"
|
||||||
|
])
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
axios.post("/api/logout").finally(() => {
|
axios.post("/api/logout").finally(() => {
|
||||||
setTimeout(() => router.reload(), 500);
|
setTimeout(() => router.reload(), 500);
|
||||||
@@ -104,9 +116,6 @@ export default function Sidebar({ path, navDisabled = false, focusMode = false,
|
|||||||
{checkAccess(user, ["student", "teacher", "developer"], permissions, "viewExams") && (
|
{checkAccess(user, ["student", "teacher", "developer"], permissions, "viewExams") && (
|
||||||
<Nav disabled={disableNavigation} Icon={BsFileEarmarkText} label="Exams" path={path} keyPath="/exam" isMinimized={isMinimized} />
|
<Nav disabled={disableNavigation} Icon={BsFileEarmarkText} label="Exams" path={path} keyPath="/exam" isMinimized={isMinimized} />
|
||||||
)}
|
)}
|
||||||
{checkAccess(user, ["student", "teacher", "developer"], permissions, "viewExercises") && (
|
|
||||||
<Nav disabled={disableNavigation} Icon={BsPencil} label="Exercises" path={path} keyPath="/exercises" isMinimized={isMinimized} />
|
|
||||||
)}
|
|
||||||
{checkAccess(user, getTypesOfUser(["agent"]), permissions, "viewStats") && (
|
{checkAccess(user, getTypesOfUser(["agent"]), permissions, "viewStats") && (
|
||||||
<Nav disabled={disableNavigation} Icon={BsGraphUp} label="Stats" path={path} keyPath="/stats" isMinimized={isMinimized} />
|
<Nav disabled={disableNavigation} Icon={BsGraphUp} label="Stats" path={path} keyPath="/stats" isMinimized={isMinimized} />
|
||||||
)}
|
)}
|
||||||
@@ -157,7 +166,7 @@ export default function Sidebar({ path, navDisabled = false, focusMode = false,
|
|||||||
badge={totalAssignedTickets}
|
badge={totalAssignedTickets}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"]) && (
|
{entitiesAllowGeneration.length > 0 && (
|
||||||
<Nav
|
<Nav
|
||||||
disabled={disableNavigation}
|
disabled={disableNavigation}
|
||||||
Icon={BsCloudFill}
|
Icon={BsCloudFill}
|
||||||
@@ -169,40 +178,29 @@ export default function Sidebar({ path, navDisabled = false, focusMode = false,
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="-xl:flex flex-col gap-3 xl:hidden">
|
<div className="-xl:flex flex-col gap-3 xl:hidden">
|
||||||
<Nav disabled={disableNavigation} Icon={MdSpaceDashboard} label="Dashboard" path={path} keyPath="/" isMinimized={true} />
|
<Nav disabled={disableNavigation} Icon={MdSpaceDashboard} label="Dashboard" path={path} keyPath="/" isMinimized />
|
||||||
<Nav disabled={disableNavigation} Icon={BsFileEarmarkText} label="Exams" path={path} keyPath="/exam" isMinimized={true} />
|
<Nav disabled={disableNavigation} Icon={BsFileEarmarkText} label="Exams" path={path} keyPath="/exam" isMinimized />
|
||||||
<Nav disabled={disableNavigation} Icon={BsPencil} label="Exercises" path={path} keyPath="/exercises" isMinimized={true} />
|
|
||||||
{checkAccess(user, getTypesOfUser(["agent"]), permissions, "viewStats") && (
|
{checkAccess(user, getTypesOfUser(["agent"]), permissions, "viewStats") && (
|
||||||
<Nav disabled={disableNavigation} Icon={BsGraphUp} label="Stats" path={path} keyPath="/stats" isMinimized={true} />
|
<Nav disabled={disableNavigation} Icon={BsGraphUp} label="Stats" path={path} keyPath="/stats" isMinimized />
|
||||||
)}
|
)}
|
||||||
{checkAccess(user, getTypesOfUser(["agent"]), permissions, "viewRecords") && (
|
{checkAccess(user, getTypesOfUser(["agent"]), permissions, "viewRecords") && (
|
||||||
<Nav disabled={disableNavigation} Icon={BsClockHistory} label="Record" path={path} keyPath="/record" isMinimized={true} />
|
<Nav disabled={disableNavigation} Icon={BsClockHistory} label="Record" path={path} keyPath="/record" isMinimized />
|
||||||
)}
|
)}
|
||||||
{checkAccess(user, getTypesOfUser(["agent"]), permissions, "viewRecords") && (
|
{checkAccess(user, getTypesOfUser(["agent"]), permissions, "viewRecords") && (
|
||||||
<Nav disabled={disableNavigation} Icon={CiDumbbell} label="Training" path={path} keyPath="/training" isMinimized={true} />
|
<Nav disabled={disableNavigation} Icon={CiDumbbell} label="Training" path={path} keyPath="/training" isMinimized />
|
||||||
)}
|
)}
|
||||||
{checkAccess(user, getTypesOfUser(["student"])) && (
|
{checkAccess(user, getTypesOfUser(["student"])) && (
|
||||||
<Nav disabled={disableNavigation} Icon={BsShieldFill} label="Settings" path={path} keyPath="/settings" isMinimized={true} />
|
<Nav disabled={disableNavigation} Icon={BsShieldFill} label="Settings" path={path} keyPath="/settings" isMinimized />
|
||||||
)}
|
)}
|
||||||
{checkAccess(user, ["developer"]) && (
|
{entitiesAllowGeneration.length > 0 && (
|
||||||
<>
|
|
||||||
<Nav
|
<Nav
|
||||||
disabled={disableNavigation}
|
disabled={disableNavigation}
|
||||||
Icon={BsCloudFill}
|
Icon={BsCloudFill}
|
||||||
label="Generation"
|
label="Generation"
|
||||||
path={path}
|
path={path}
|
||||||
keyPath="/generation"
|
keyPath="/generation"
|
||||||
isMinimized={true}
|
isMinimized
|
||||||
/>
|
/>
|
||||||
<Nav
|
|
||||||
disabled={disableNavigation}
|
|
||||||
Icon={BsFileLock}
|
|
||||||
label="Permissions"
|
|
||||||
path={path}
|
|
||||||
keyPath="/permissions"
|
|
||||||
isMinimized={true}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export default function AssignmentView({isOpen, users, assignment, onClose}: Pro
|
|||||||
.sort(sortByModule)
|
.sort(sortByModule)
|
||||||
.map((x) => x!.module),
|
.map((x) => x!.module),
|
||||||
);
|
);
|
||||||
router.push("/exercises");
|
router.push("/exam");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export default function StudentDashboard({user, linkedCorporate}: Props) {
|
|||||||
);
|
);
|
||||||
setAssignment(assignment);
|
setAssignment(assignment);
|
||||||
|
|
||||||
router.push("/exercises");
|
router.push("/exam");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { EntityWithRoles } from "@/interfaces/entity";
|
|||||||
import { User } from "@/interfaces/user";
|
import { User } from "@/interfaces/user";
|
||||||
import { RolePermission } from "@/resources/entityPermissions";
|
import { RolePermission } from "@/resources/entityPermissions";
|
||||||
import { mapBy } from "@/utils";
|
import { mapBy } from "@/utils";
|
||||||
import { doesEntityAllow, findAllowedEntities } from "@/utils/permissions";
|
import { doesEntityAllow, findAllowedEntities, findAllowedEntitiesSomePermissions } from "@/utils/permissions";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
|
|
||||||
export const useAllowedEntities = (user: User, entities: EntityWithRoles[], permission: RolePermission) => {
|
export const useAllowedEntities = (user: User, entities: EntityWithRoles[], permission: RolePermission) => {
|
||||||
@@ -10,6 +10,11 @@ export const useAllowedEntities = (user: User, entities: EntityWithRoles[], perm
|
|||||||
return allowedEntityIds
|
return allowedEntityIds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useAllowedEntitiesSomePermissions = (user: User, entities: EntityWithRoles[], permissions: RolePermission[]) => {
|
||||||
|
const allowedEntityIds = useMemo(() => findAllowedEntitiesSomePermissions(user, entities, permissions), [user, entities, permissions])
|
||||||
|
return allowedEntityIds
|
||||||
|
}
|
||||||
|
|
||||||
export const useEntityPermission = (user: User, entity: EntityWithRoles, permission: RolePermission) => {
|
export const useEntityPermission = (user: User, entity: EntityWithRoles, permission: RolePermission) => {
|
||||||
const isAllowed = useMemo(() => doesEntityAllow(user, entity, permission), [user, entity, permission])
|
const isAllowed = useMemo(() => doesEntityAllow(user, entity, permission), [user, entity, permission])
|
||||||
return isAllowed
|
return isAllowed
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { RolePermission } from "@/resources/entityPermissions";
|
||||||
|
|
||||||
export interface Entity {
|
export interface Entity {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -6,7 +8,7 @@ export interface Entity {
|
|||||||
export interface Role {
|
export interface Role {
|
||||||
id: string;
|
id: string;
|
||||||
entityID: string;
|
entityID: string;
|
||||||
permissions: string[];
|
permissions: RolePermission[];
|
||||||
label: string;
|
label: string;
|
||||||
isDefault?: boolean
|
isDefault?: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export default function BatchCreateUser({user, users, entities = [], permissions
|
|||||||
<th className="border border-neutral-200 px-2 py-1">E-mail</th>
|
<th className="border border-neutral-200 px-2 py-1">E-mail</th>
|
||||||
<th className="border border-neutral-200 px-2 py-1">Phone Number</th>
|
<th className="border border-neutral-200 px-2 py-1">Phone Number</th>
|
||||||
{user?.type !== "corporate" && <th className="border border-neutral-200 px-2 py-1">Corporate (e-mail)</th>}
|
{user?.type !== "corporate" && <th className="border border-neutral-200 px-2 py-1">Corporate (e-mail)</th>}
|
||||||
<th className="border border-neutral-200 px-2 py-1">Group Name</th>
|
<th className="border border-neutral-200 px-2 py-1">Classroom Name</th>
|
||||||
<th className="border border-neutral-200 px-2 py-1">Country</th>
|
<th className="border border-neutral-200 px-2 py-1">Country</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default function ExamLoader() {
|
|||||||
setExams([exam]);
|
setExams([exam]);
|
||||||
setSelectedModules([selectedModule]);
|
setSelectedModules([selectedModule]);
|
||||||
|
|
||||||
router.push("/exercises");
|
router.push("/exam");
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import Modal from "@/components/Modal";
|
|||||||
import {checkAccess} from "@/utils/permissions";
|
import {checkAccess} from "@/utils/permissions";
|
||||||
import useGroups from "@/hooks/useGroups";
|
import useGroups from "@/hooks/useGroups";
|
||||||
import Button from "@/components/Low/Button";
|
import Button from "@/components/Low/Button";
|
||||||
|
import { EntityWithRoles } from "@/interfaces/entity";
|
||||||
|
import { useAllowedEntities } from "@/hooks/useEntityPermissions";
|
||||||
|
|
||||||
const searchFields = [["module"], ["id"], ["createdBy"]];
|
const searchFields = [["module"], ["id"], ["createdBy"]];
|
||||||
|
|
||||||
@@ -56,13 +58,36 @@ const ExamOwnerSelector = ({options, exam, onSave}: {options: User[]; exam: Exam
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ExamList({user}: {user: User}) {
|
export default function ExamList({user, entities = []}: {user: User, entities: EntityWithRoles[]}) {
|
||||||
const [selectedExam, setSelectedExam] = useState<Exam>();
|
const [selectedExam, setSelectedExam] = useState<Exam>();
|
||||||
|
|
||||||
const {exams, reload} = useExams();
|
const {exams, reload} = useExams();
|
||||||
const {users} = useUsers();
|
const {users} = useUsers();
|
||||||
const {groups} = useGroups({admin: user?.id, userType: user?.type});
|
const {groups} = useGroups({admin: user?.id, userType: user?.type});
|
||||||
|
|
||||||
|
const canViewReading = useAllowedEntities(user, entities, 'view_reading')
|
||||||
|
const canViewListening = useAllowedEntities(user, entities, 'view_listening')
|
||||||
|
const canViewWriting = useAllowedEntities(user, entities, 'view_writing')
|
||||||
|
const canViewSpeaking = useAllowedEntities(user, entities, 'view_speaking')
|
||||||
|
const canViewLevel = useAllowedEntities(user, entities, 'view_level')
|
||||||
|
|
||||||
|
const canDeleteReading = useAllowedEntities(user, entities, 'delete_reading')
|
||||||
|
const canDeleteListening = useAllowedEntities(user, entities, 'delete_listening')
|
||||||
|
const canDeleteWriting = useAllowedEntities(user, entities, 'delete_writing')
|
||||||
|
const canDeleteSpeaking = useAllowedEntities(user, entities, 'delete_speaking')
|
||||||
|
const canDeleteLevel = useAllowedEntities(user, entities, 'delete_level')
|
||||||
|
|
||||||
|
const modulePermissions = useMemo(() => ({
|
||||||
|
reading: {view: canViewReading.length > 0, delete: canDeleteReading.length > 0},
|
||||||
|
listening: {view: canViewListening.length > 0, delete: canDeleteListening.length > 0},
|
||||||
|
writing: {view: canViewWriting.length > 0, delete: canDeleteWriting.length > 0},
|
||||||
|
speaking: {view: canViewSpeaking.length > 0, delete: canDeleteSpeaking.length > 0},
|
||||||
|
level: {view: canViewLevel.length > 0, delete: canDeleteLevel.length > 0},
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
canDeleteLevel.length, canDeleteListening.length, canDeleteReading.length, canDeleteSpeaking.length, canDeleteWriting.length, canViewLevel.length, canViewListening.length, canViewReading.length, canViewSpeaking.length, canViewWriting.length
|
||||||
|
])
|
||||||
|
|
||||||
const filteredCorporates = useMemo(() => {
|
const filteredCorporates = useMemo(() => {
|
||||||
const participantsAndAdmins = uniq(groups.flatMap((x) => [...x.participants, x.admin])).filter((x) => x !== user?.id);
|
const participantsAndAdmins = uniq(groups.flatMap((x) => [...x.participants, x.admin])).filter((x) => x !== user?.id);
|
||||||
return users.filter((x) => participantsAndAdmins.includes(x.id) && x.type === "corporate");
|
return users.filter((x) => participantsAndAdmins.includes(x.id) && x.type === "corporate");
|
||||||
@@ -84,7 +109,10 @@ export default function ExamList({user}: {user: User}) {
|
|||||||
});
|
});
|
||||||
}, [exams, users]);
|
}, [exams, users]);
|
||||||
|
|
||||||
const {rows: filteredRows, renderSearch} = useListSearch<Exam>(searchFields, parsedExams);
|
const filteredExams = useMemo(() => parsedExams.filter(({module}) => modulePermissions[module].view),
|
||||||
|
[parsedExams, modulePermissions])
|
||||||
|
|
||||||
|
const {rows: filteredRows, renderSearch} = useListSearch<Exam>(searchFields, filteredExams);
|
||||||
|
|
||||||
const setExams = useExamStore((state) => state.setExams);
|
const setExams = useExamStore((state) => state.setExams);
|
||||||
const setSelectedModules = useExamStore((state) => state.setSelectedModules);
|
const setSelectedModules = useExamStore((state) => state.setSelectedModules);
|
||||||
@@ -104,7 +132,7 @@ export default function ExamList({user}: {user: User}) {
|
|||||||
setExams([exam]);
|
setExams([exam]);
|
||||||
setSelectedModules([module]);
|
setSelectedModules([module]);
|
||||||
|
|
||||||
router.push("/exercises");
|
router.push("/exam");
|
||||||
};
|
};
|
||||||
|
|
||||||
const privatizeExam = async (exam: Exam) => {
|
const privatizeExam = async (exam: Exam) => {
|
||||||
@@ -245,7 +273,7 @@ export default function ExamList({user}: {user: User}) {
|
|||||||
onClick={async () => await loadExam(row.original.module, row.original.id)}>
|
onClick={async () => await loadExam(row.original.module, row.original.id)}>
|
||||||
<BsUpload className="hover:text-mti-purple-light transition ease-in-out duration-300" />
|
<BsUpload className="hover:text-mti-purple-light transition ease-in-out duration-300" />
|
||||||
</button>
|
</button>
|
||||||
{PERMISSIONS.examManagement.delete.includes(user.type) && (
|
{modulePermissions[row.original.module].delete && (
|
||||||
<div data-tip="Delete" className="cursor-pointer tooltip" onClick={() => deleteExam(row.original)}>
|
<div data-tip="Delete" className="cursor-pointer tooltip" onClick={() => deleteExam(row.original)}>
|
||||||
<BsTrash className="hover:text-mti-purple-light transition ease-in-out duration-300" />
|
<BsTrash className="hover:text-mti-purple-light transition ease-in-out duration-300" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,16 +8,24 @@ import GroupList from "./GroupList";
|
|||||||
import PackageList from "./PackageList";
|
import PackageList from "./PackageList";
|
||||||
import UserList from "./UserList";
|
import UserList from "./UserList";
|
||||||
import {checkAccess} from "@/utils/permissions";
|
import {checkAccess} from "@/utils/permissions";
|
||||||
import usePermissions from "@/hooks/usePermissions";
|
|
||||||
import {PermissionType} from "@/interfaces/permissions";
|
import {PermissionType} from "@/interfaces/permissions";
|
||||||
|
import { EntityWithRoles } from "@/interfaces/entity";
|
||||||
|
import { useAllowedEntitiesSomePermissions } from "@/hooks/useEntityPermissions";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: User;
|
user: User;
|
||||||
users: User[];
|
entities: EntityWithRoles[]
|
||||||
permissions: PermissionType[];
|
permissions: PermissionType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Lists({user, users, permissions}: Props) {
|
export default function Lists({user, entities = [], permissions}: Props) {
|
||||||
|
const entitiesViewExams = useAllowedEntitiesSomePermissions(user, entities, [
|
||||||
|
'view_reading', 'view_listening', 'view_writing', 'view_speaking', 'view_level'
|
||||||
|
])
|
||||||
|
|
||||||
|
const canViewExams = useMemo(() => entitiesViewExams.length > 0, [entitiesViewExams])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabGroup>
|
<TabGroup>
|
||||||
<TabList className="flex space-x-1 rounded-xl bg-mti-purple-ultralight/40 p-1">
|
<TabList className="flex space-x-1 rounded-xl bg-mti-purple-ultralight/40 p-1">
|
||||||
@@ -32,7 +40,7 @@ export default function Lists({user, users, permissions}: Props) {
|
|||||||
}>
|
}>
|
||||||
User List
|
User List
|
||||||
</Tab>
|
</Tab>
|
||||||
{checkAccess(user, ["developer", "admin", "corporate", "mastercorporate", "teacher"]) && (
|
{canViewExams && (
|
||||||
<Tab
|
<Tab
|
||||||
className={({selected}) =>
|
className={({selected}) =>
|
||||||
clsx(
|
clsx(
|
||||||
@@ -45,17 +53,6 @@ export default function Lists({user, users, permissions}: Props) {
|
|||||||
Exam List
|
Exam List
|
||||||
</Tab>
|
</Tab>
|
||||||
)}
|
)}
|
||||||
<Tab
|
|
||||||
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",
|
|
||||||
"transition duration-300 ease-in-out",
|
|
||||||
selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-mti-purple-dark",
|
|
||||||
)
|
|
||||||
}>
|
|
||||||
Group List
|
|
||||||
</Tab>
|
|
||||||
{checkAccess(user, ["developer", "admin", "corporate"]) && (
|
{checkAccess(user, ["developer", "admin", "corporate"]) && (
|
||||||
<Tab
|
<Tab
|
||||||
className={({selected}) =>
|
className={({selected}) =>
|
||||||
@@ -100,14 +97,11 @@ export default function Lists({user, users, permissions}: Props) {
|
|||||||
<TabPanel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
|
<TabPanel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
|
||||||
<UserList user={user} />
|
<UserList user={user} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
{checkAccess(user, ["developer", "admin", "corporate", "mastercorporate", "teacher"]) && (
|
{canViewExams && (
|
||||||
<TabPanel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
|
<TabPanel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
|
||||||
<ExamList user={user} />
|
<ExamList user={user} entities={entities} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
)}
|
)}
|
||||||
<TabPanel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
|
|
||||||
<GroupList user={user} />
|
|
||||||
</TabPanel>
|
|
||||||
{checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"], permissions, "viewCodes") && (
|
{checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"], permissions, "viewCodes") && (
|
||||||
<TabPanel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
|
<TabPanel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
|
||||||
<CodeList user={user} />
|
<CodeList user={user} />
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ export default function UserCreator({ user, users, entities = [], permissions, o
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={clsx("flex flex-col gap-4")}>
|
<div className={clsx("flex flex-col gap-4")}>
|
||||||
<label className="font-normal text-base text-mti-gray-dim">Group</label>
|
<label className="font-normal text-base text-mti-gray-dim">Classroom</label>
|
||||||
<Select
|
<Select
|
||||||
options={groups
|
options={groups
|
||||||
.filter((x) => x.entity?.id === entity)
|
.filter((x) => x.entity?.id === entity)
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ const LevelGeneration = ({ id }: Props) => {
|
|||||||
setExams([exam]);
|
setExams([exam]);
|
||||||
setSelectedModules(["level"]);
|
setSelectedModules(["level"]);
|
||||||
|
|
||||||
router.push("/exercises");
|
router.push("/exam");
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateExam = () => {
|
const generateExam = () => {
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ const ListeningGeneration = ({id}: Props) => {
|
|||||||
setExams([exam]);
|
setExams([exam]);
|
||||||
setSelectedModules(["listening"]);
|
setSelectedModules(["listening"]);
|
||||||
|
|
||||||
router.push("/exercises");
|
router.push("/exam");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ const ReadingGeneration = ({id}: Props) => {
|
|||||||
setExams([exam]);
|
setExams([exam]);
|
||||||
setSelectedModules(["reading"]);
|
setSelectedModules(["reading"]);
|
||||||
|
|
||||||
router.push("/exercises");
|
router.push("/exam");
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitExam = () => {
|
const submitExam = () => {
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ const SpeakingGeneration = ({id}: Props) => {
|
|||||||
setExams([exam]);
|
setExams([exam]);
|
||||||
setSelectedModules(["speaking"]);
|
setSelectedModules(["speaking"]);
|
||||||
|
|
||||||
router.push("/exercises");
|
router.push("/exam");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ const WritingGeneration = ({id}: Props) => {
|
|||||||
setExams([exam]);
|
setExams([exam]);
|
||||||
setSelectedModules(["writing"]);
|
setSelectedModules(["writing"]);
|
||||||
|
|
||||||
router.push("/exercises");
|
router.push("/exam");
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitExam = () => {
|
const submitExam = () => {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default function App({Component, pageProps}: AppProps) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (router.pathname !== "/exercises") reset();
|
if (router.pathname !== "/exam" && router.pathname !== "/exercises") reset();
|
||||||
}, [router.pathname, reset]);
|
}, [router.pathname, reset]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ export default function AssignmentView({user, users, entity, assignment}: Props)
|
|||||||
.sort(sortByModule)
|
.sort(sortByModule)
|
||||||
.map((x) => x!.module),
|
.map((x) => x!.module),
|
||||||
);
|
);
|
||||||
router.push("/exercises");
|
router.push("/exam");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export default function AssignmentsPage({assignment, user, users, entities, grou
|
|||||||
const [selectedModules, setSelectedModules] = useState<Module[]>(assignment.exams.map((e) => e.module));
|
const [selectedModules, setSelectedModules] = useState<Module[]>(assignment.exams.map((e) => e.module));
|
||||||
const [assignees, setAssignees] = useState<string[]>(assignment.assignees);
|
const [assignees, setAssignees] = useState<string[]>(assignment.assignees);
|
||||||
const [teachers, setTeachers] = useState<string[]>(assignment.teachers || []);
|
const [teachers, setTeachers] = useState<string[]>(assignment.teachers || []);
|
||||||
const [entity, setEntity] = useState<string | undefined>(entities[0]?.id);
|
const [entity, setEntity] = useState<string | undefined>(assignment.entity || entities[0]?.id);
|
||||||
const [name, setName] = useState(assignment.name);
|
const [name, setName] = useState(assignment.name);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export default function AssignmentsPage({assignments, corporateAssignments, enti
|
|||||||
{...a}
|
{...a}
|
||||||
users={users}
|
users={users}
|
||||||
onClick={
|
onClick={
|
||||||
entitiesAllowEdit.length > 0
|
mapBy(entitiesAllowEdit, 'id').includes(a.entity || "")
|
||||||
? () => router.push(`/assignments/creator/${a.id}`)
|
? () => router.push(`/assignments/creator/${a.id}`)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,14 +132,14 @@ export default function Home({user, group, users, entity}: Props) {
|
|||||||
const renameGroup = () => {
|
const renameGroup = () => {
|
||||||
if (!canRenameClassroom) return;
|
if (!canRenameClassroom) return;
|
||||||
|
|
||||||
const name = prompt("Rename this group:", group.name);
|
const name = prompt("Rename this classroom:", group.name);
|
||||||
if (!name) return;
|
if (!name) return;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
axios
|
axios
|
||||||
.patch(`/api/groups/${group.id}`, {name})
|
.patch(`/api/groups/${group.id}`, {name})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("The group has been updated successfully!");
|
toast.success("The classroom has been updated successfully!");
|
||||||
router.replace(router.asPath);
|
router.replace(router.asPath);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
@@ -151,14 +151,14 @@ export default function Home({user, group, users, entity}: Props) {
|
|||||||
|
|
||||||
const deleteGroup = () => {
|
const deleteGroup = () => {
|
||||||
if (!canDeleteClassroom) return;
|
if (!canDeleteClassroom) return;
|
||||||
if (!confirm("Are you sure you want to delete this group?")) return;
|
if (!confirm("Are you sure you want to delete this classroom?")) return;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.delete(`/api/groups/${group.id}`)
|
.delete(`/api/groups/${group.id}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("This group has been successfully deleted!");
|
toast.success("This classroom has been successfully deleted!");
|
||||||
router.replace("/classrooms");
|
router.replace("/classrooms");
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
@@ -203,14 +203,14 @@ export default function Home({user, group, users, entity}: Props) {
|
|||||||
disabled={isLoading || !canRenameClassroom}
|
disabled={isLoading || !canRenameClassroom}
|
||||||
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">
|
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">
|
||||||
<BsTag />
|
<BsTag />
|
||||||
<span className="text-xs">Rename Group</span>
|
<span className="text-xs">Rename Classroom</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={deleteGroup}
|
onClick={deleteGroup}
|
||||||
disabled={isLoading || !canDeleteClassroom}
|
disabled={isLoading || !canDeleteClassroom}
|
||||||
className="flex items-center gap-1 px-2 py-2 border border-mti-rose rounded-full bg-mti-rose-light text-white hover:bg-mti-rose-dark disabled:hover:bg-mti-rose-light disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition ease-in-out duration-300">
|
className="flex items-center gap-1 px-2 py-2 border border-mti-rose rounded-full bg-mti-rose-light text-white hover:bg-mti-rose-dark disabled:hover:bg-mti-rose-light disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition ease-in-out duration-300">
|
||||||
<BsTrash />
|
<BsTrash />
|
||||||
<span className="text-xs">Delete Group</span>
|
<span className="text-xs">Delete Classroom</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export default function Home({user, groups, entities}: Props) {
|
|||||||
href={`/classrooms/create`}
|
href={`/classrooms/create`}
|
||||||
className="p-4 border hover:text-mti-purple rounded-xl flex flex-col items-center justify-center gap-0 hover:border-mti-purple transition ease-in-out duration-300 text-left cursor-pointer">
|
className="p-4 border hover:text-mti-purple rounded-xl flex flex-col items-center justify-center gap-0 hover:border-mti-purple transition ease-in-out duration-300 text-left cursor-pointer">
|
||||||
<BsPlus size={40} />
|
<BsPlus size={40} />
|
||||||
<span className="font-semibold">Create Group</span>
|
<span className="font-semibold">Create Classroom</span>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export default function Dashboard({user, entities, assignments, stats, invites,
|
|||||||
);
|
);
|
||||||
setAssignment(assignment);
|
setAssignment(assignment);
|
||||||
|
|
||||||
router.push("/exercises");
|
router.push("/exam");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,13 @@ export default function Dashboard({ user, users, entities, assignments, stats, g
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<section className="grid grid-cols-5 -md:grid-cols-2 place-items-center gap-4 text-center">
|
<section className="grid grid-cols-5 -md:grid-cols-2 place-items-center gap-4 text-center">
|
||||||
<IconCard Icon={BsPersonFill} label="Students" value={students.length} color="purple" />
|
<IconCard
|
||||||
|
Icon={BsPersonFill}
|
||||||
|
onClick={() => router.push("/users?type=student")}
|
||||||
|
label="Students"
|
||||||
|
value={students.length}
|
||||||
|
color="purple"
|
||||||
|
/>
|
||||||
<IconCard
|
<IconCard
|
||||||
onClick={() => router.push("/classrooms")}
|
onClick={() => router.push("/classrooms")}
|
||||||
Icon={BsPeople}
|
Icon={BsPeople}
|
||||||
|
|||||||
@@ -45,14 +45,19 @@ const USER_MANAGEMENT: PermissionLayout[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const EXAM_MANAGEMENT: PermissionLayout[] = [
|
const EXAM_MANAGEMENT: PermissionLayout[] = [
|
||||||
|
{label: "View Reading", key: "view_reading"},
|
||||||
{label: "Generate Reading", key: "generate_reading"},
|
{label: "Generate Reading", key: "generate_reading"},
|
||||||
{label: "Delete Reading", key: "delete_reading"},
|
{label: "Delete Reading", key: "delete_reading"},
|
||||||
|
{label: "View Listening", key: "view_listening"},
|
||||||
{label: "Generate Listening", key: "generate_listening"},
|
{label: "Generate Listening", key: "generate_listening"},
|
||||||
{label: "Delete Listening", key: "delete_listening"},
|
{label: "Delete Listening", key: "delete_listening"},
|
||||||
|
{label: "View Writing", key: "view_writing"},
|
||||||
{label: "Generate Writing", key: "generate_writing"},
|
{label: "Generate Writing", key: "generate_writing"},
|
||||||
{label: "Delete Writing", key: "delete_writing"},
|
{label: "Delete Writing", key: "delete_writing"},
|
||||||
|
{label: "View Speaking", key: "view_speaking"},
|
||||||
{label: "Generate Speaking", key: "generate_speaking"},
|
{label: "Generate Speaking", key: "generate_speaking"},
|
||||||
{label: "Delete Speaking", key: "delete_speaking"},
|
{label: "Delete Speaking", key: "delete_speaking"},
|
||||||
|
{label: "View Level", key: "view_level"},
|
||||||
{label: "Generate Level", key: "generate_level"},
|
{label: "Generate Level", key: "generate_level"},
|
||||||
{label: "Delete Level", key: "delete_level"},
|
{label: "Delete Level", key: "delete_level"},
|
||||||
]
|
]
|
||||||
@@ -192,7 +197,7 @@ export default function Role({user, entity, role, userCount}: Props) {
|
|||||||
.finally(() => setIsLoading(false));
|
.finally(() => setIsLoading(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
const togglePermissions = (p: string) => setPermissions(prev => prev.includes(p) ? prev.filter(x => x !== p) : [...prev, p])
|
const togglePermissions = (p: RolePermission) => setPermissions(prev => prev.includes(p) ? prev.filter(x => x !== p) : [...prev, p])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -278,7 +283,7 @@ export default function Role({user, entity, role, userCount}: Props) {
|
|||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-3 gap-4">
|
||||||
{EXAM_MANAGEMENT.map(({label, key}) => (
|
{EXAM_MANAGEMENT.map(({label, key}) => (
|
||||||
<Checkbox disabled={!canEditPermissions} key={key} isChecked={permissions.includes(key)} onChange={() => togglePermissions(key)}>
|
<Checkbox disabled={!canEditPermissions} key={key} isChecked={permissions.includes(key)} onChange={() => togglePermissions(key)}>
|
||||||
{ label }
|
{ label }
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res, query}) =
|
|||||||
if (assignmentID) {
|
if (assignmentID) {
|
||||||
const assignment = await getAssignment(assignmentID)
|
const assignment = await getAssignment(assignmentID)
|
||||||
|
|
||||||
if (!assignment) return redirect("/exercises")
|
if (!assignment) return redirect("/exam")
|
||||||
if (!["admin", "developer"].includes(user.type) && !assignment.assignees.includes(user.id)) return redirect("/exercises")
|
if (!["admin", "developer"].includes(user.type) && !assignment.assignees.includes(user.id)) return redirect("/exercises")
|
||||||
|
|
||||||
const exams = await getExamsByIds(uniqBy(assignment.exams, "id"))
|
const exams = await getExamsByIds(uniqBy(assignment.exams, "id"))
|
||||||
@@ -44,7 +44,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res, query}) =
|
|||||||
moment(assignment.startDate).isBefore(moment()) ||
|
moment(assignment.startDate).isBefore(moment()) ||
|
||||||
moment(assignment.endDate).isAfter(moment())
|
moment(assignment.endDate).isAfter(moment())
|
||||||
)
|
)
|
||||||
return redirect("/exercises")
|
return redirect("/exam")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: serialize({user, assignment, exams, session})
|
props: serialize({user, assignment, exams, session})
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export default function Admin({ user, entities, permissions }: Props) {
|
|||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
<section className="w-full">
|
<section className="w-full">
|
||||||
<Lists user={user} users={users} permissions={permissions} />
|
<Lists user={user} entities={entities} permissions={permissions} />
|
||||||
</section>
|
</section>
|
||||||
</Layout>
|
</Layout>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ const TrainingContent: React.FC<{user: User}> = ({user}) => {
|
|||||||
.sort(sortByModule)
|
.sort(sortByModule)
|
||||||
.map((x) => x!.module),
|
.map((x) => x!.module),
|
||||||
);
|
);
|
||||||
router.push("/exercises");
|
router.push("/exam");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,14 +12,19 @@ export type RolePermission =
|
|||||||
"delete_corporates" |
|
"delete_corporates" |
|
||||||
"delete_mastercorporates" |
|
"delete_mastercorporates" |
|
||||||
"generate_reading" |
|
"generate_reading" |
|
||||||
|
"view_reading" |
|
||||||
"delete_reading" |
|
"delete_reading" |
|
||||||
"generate_listening" |
|
"generate_listening" |
|
||||||
|
"view_listening" |
|
||||||
"delete_listening" |
|
"delete_listening" |
|
||||||
"generate_writing" |
|
"generate_writing" |
|
||||||
|
"view_writing" |
|
||||||
"delete_writing" |
|
"delete_writing" |
|
||||||
"generate_speaking" |
|
"generate_speaking" |
|
||||||
|
"view_speaking" |
|
||||||
"delete_speaking" |
|
"delete_speaking" |
|
||||||
"generate_level" |
|
"generate_level" |
|
||||||
|
"view_level" |
|
||||||
"delete_level" |
|
"delete_level" |
|
||||||
"view_classrooms" |
|
"view_classrooms" |
|
||||||
"create_classroom" |
|
"create_classroom" |
|
||||||
@@ -27,7 +32,8 @@ export type RolePermission =
|
|||||||
"add_to_classroom" |
|
"add_to_classroom" |
|
||||||
"remove_from_classroom" |
|
"remove_from_classroom" |
|
||||||
"delete_classroom" |
|
"delete_classroom" |
|
||||||
"view_entities" | "rename_entity" |
|
"view_entities" |
|
||||||
|
"rename_entity" |
|
||||||
"add_to_entity" |
|
"add_to_entity" |
|
||||||
"remove_from_entity" |
|
"remove_from_entity" |
|
||||||
"delete_entity" |
|
"delete_entity" |
|
||||||
@@ -42,7 +48,7 @@ export type RolePermission =
|
|||||||
"edit_assignment" |
|
"edit_assignment" |
|
||||||
"delete_assignment" |
|
"delete_assignment" |
|
||||||
"start_assignment" |
|
"start_assignment" |
|
||||||
"archive_assignment";
|
"archive_assignment"
|
||||||
|
|
||||||
export const DEFAULT_PERMISSIONS: RolePermission[] = [
|
export const DEFAULT_PERMISSIONS: RolePermission[] = [
|
||||||
"view_students",
|
"view_students",
|
||||||
@@ -66,14 +72,19 @@ export const ADMIN_PERMISSIONS: RolePermission[] = [
|
|||||||
"delete_corporates",
|
"delete_corporates",
|
||||||
"delete_mastercorporates",
|
"delete_mastercorporates",
|
||||||
"generate_reading",
|
"generate_reading",
|
||||||
|
"view_reading",
|
||||||
"delete_reading",
|
"delete_reading",
|
||||||
"generate_listening",
|
"generate_listening",
|
||||||
|
"view_listening",
|
||||||
"delete_listening",
|
"delete_listening",
|
||||||
"generate_writing",
|
"generate_writing",
|
||||||
|
"view_writing",
|
||||||
"delete_writing",
|
"delete_writing",
|
||||||
"generate_speaking",
|
"generate_speaking",
|
||||||
|
"view_speaking",
|
||||||
"delete_speaking",
|
"delete_speaking",
|
||||||
"generate_level",
|
"generate_level",
|
||||||
|
"view_level",
|
||||||
"delete_level",
|
"delete_level",
|
||||||
"view_classrooms",
|
"view_classrooms",
|
||||||
"create_classroom",
|
"create_classroom",
|
||||||
@@ -97,5 +108,5 @@ export const ADMIN_PERMISSIONS: RolePermission[] = [
|
|||||||
"edit_assignment",
|
"edit_assignment",
|
||||||
"delete_assignment",
|
"delete_assignment",
|
||||||
"start_assignment",
|
"start_assignment",
|
||||||
"archive_assignment"
|
"archive_assignment",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -42,6 +42,13 @@ export function findAllowedEntities(user: User, entities: EntityWithRoles[], per
|
|||||||
return allowedEntities
|
return allowedEntities
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findAllowedEntitiesSomePermissions(user: User, entities: EntityWithRoles[], permissions: RolePermission[]) {
|
||||||
|
if (["admin", "developer"].includes(user?.type)) return entities
|
||||||
|
|
||||||
|
const allowedEntities = entities.filter((e) => permissions.some((p) => doesEntityAllow(user, e, p)))
|
||||||
|
return allowedEntities
|
||||||
|
}
|
||||||
|
|
||||||
export function doesEntityAllow(user: User, entity: EntityWithRoles, permission: RolePermission) {
|
export function doesEntityAllow(user: User, entity: EntityWithRoles, permission: RolePermission) {
|
||||||
if (["admin", "developer"].includes(user?.type)) return true
|
if (["admin", "developer"].includes(user?.type)) return true
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const getFieldValue = (fields: string[], data: any): string | string[] => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const search = (text: string, fields: string[][], rows: any[]) => {
|
export const search = <T>(text: string, fields: string[][], rows: T[]) => {
|
||||||
const searchText = text.toLowerCase();
|
const searchText = text.toLowerCase();
|
||||||
return rows.filter((row) => {
|
return rows.filter((row) => {
|
||||||
return fields.some((fieldsKeys) => {
|
return fields.some((fieldsKeys) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user