diff --git a/src/components/Medium/RecordFilter.tsx b/src/components/Medium/RecordFilter.tsx index a8e5ce89..d3cbdc31 100644 --- a/src/components/Medium/RecordFilter.tsx +++ b/src/components/Medium/RecordFilter.tsx @@ -8,163 +8,166 @@ import useGroups from "@/hooks/useGroups"; import useRecordStore from "@/stores/recordStore"; import { EntityWithRoles } from "@/interfaces/entity"; import { mapBy } from "@/utils"; +import { useAllowedEntities } from "@/hooks/useEntityPermissions"; type TimeFilter = "months" | "weeks" | "days"; type Filter = TimeFilter | "assignments" | undefined; interface Props { - user: User; - entities: EntityWithRoles[] - users: User[] - filterState: { - filter: Filter, - setFilter: React.Dispatch> - }, - assignments?: boolean; - children?: ReactNode + user: User; + entities: EntityWithRoles[] + users: User[] + filterState: { + filter: Filter, + setFilter: React.Dispatch> + }, + assignments?: boolean; + children?: ReactNode } const defaultSelectableCorporate = { - value: "", - label: "All", + value: "", + label: "All", }; const RecordFilter: React.FC = ({ - user, - entities, - users, - filterState, - assignments = true, - children + user, + entities, + users, + filterState, + assignments = true, + children }) => { - const { filter, setFilter } = filterState; + const { filter, setFilter } = filterState; - const [entity, setEntity] = useState() + const [entity, setEntity] = useState() - const [, setStatsUserId] = useRecordStore((state) => [ - state.selectedUser, - state.setSelectedUser - ]); + const [, setStatsUserId] = useRecordStore((state) => [ + state.selectedUser, + state.setSelectedUser + ]); - const entityUsers = useMemo(() => !entity ? users : users.filter(u => mapBy(u.entities, 'id').includes(entity)), [users, entity]) + const allowedViewEntities = useAllowedEntities(user, entities, 'view_student_record') - useEffect(() => setStatsUserId(user.id), [setStatsUserId, user.id]) + const entityUsers = useMemo(() => !entity ? users : users.filter(u => mapBy(u.entities, 'id').includes(entity)), [users, entity]) - const toggleFilter = (value: "months" | "weeks" | "days" | "assignments") => { - setFilter((prev) => (prev === value ? undefined : value)); - }; + useEffect(() => setStatsUserId(user.id), [setStatsUserId, user.id]) - return ( -
-
- {checkAccess(user, ["developer", "admin", "mastercorporate"]) && !children && ( - <> -
- + const toggleFilter = (value: "months" | "weeks" | "days" | "assignments") => { + setFilter((prev) => (prev === value ? undefined : value)); + }; - ({ - value: x.id, - label: `${x.name} - ${x.email}`, - }))} - defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}} - onChange={(value) => setStatsUserId(value?.value!)} - styles={{ - menuPortal: (base) => ({ ...base, zIndex: 9999 }), - option: (styles, state) => ({ - ...styles, - backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", - color: state.isFocused ? "black" : styles.color, - }), - }} - /> -
- - )} - {(user.type === "corporate" || user.type === "teacher") && !children && ( -
- + ({ - value: x.id, - label: `${x.name} - ${x.email}`, - }))} - defaultValue={{value: user.id, label: `${user.name} - ${user.email}`}} - onChange={(value) => setStatsUserId(value?.value!)} - styles={{ - menuPortal: (base) => ({ ...base, zIndex: 9999 }), - option: (styles, state) => ({ - ...styles, - backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", - color: state.isFocused ? "black" : styles.color, - }), - }} - /> -
- )} - {children} -
-
- {assignments && ( - - )} - - - -
-
- ); + ({ + value: x.id, + label: `${x.name} - ${x.email}`, + }))} + defaultValue={{ value: user.id, label: `${user.name} - ${user.email}` }} + onChange={(value) => setStatsUserId(value?.value!)} + styles={{ + menuPortal: (base) => ({ ...base, zIndex: 9999 }), + option: (styles, state) => ({ + ...styles, + backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white", + color: state.isFocused ? "black" : styles.color, + }), + }} + /> + + )} + {children} + +
+ {assignments && ( + + )} + + + +
+ + ); } export default RecordFilter; diff --git a/src/components/Medium/StatGridItem.tsx b/src/components/Medium/StatGridItem.tsx index aa025517..976edd99 100644 --- a/src/components/Medium/StatGridItem.tsx +++ b/src/components/Medium/StatGridItem.tsx @@ -82,7 +82,7 @@ interface StatsGridItemProps { selectedTrainingExams?: string[]; maxTrainingExams?: number; setSelectedTrainingExams?: React.Dispatch>; - renderPdfIcon: (session: string, color: string, textColor: string) => React.ReactNode; + renderPdfIcon?: (session: string, color: string, textColor: string) => React.ReactNode; } const StatsGridItem: React.FC = ({ @@ -236,7 +236,7 @@ const StatsGridItem: React.FC = ({ {renderLevelScore()} )} - {shouldRenderPDFIcon() && renderPdfIcon(session, textColor, textColor)} + {shouldRenderPDFIcon() && renderPdfIcon && renderPdfIcon(session, textColor, textColor)} {examNumber === undefined ? ( <> diff --git a/src/pages/entities/[id]/roles/[role].tsx b/src/pages/entities/[id]/roles/[role].tsx index 40c8f37c..037fb571 100644 --- a/src/pages/entities/[id]/roles/[role].tsx +++ b/src/pages/entities/[id]/roles/[role].tsx @@ -79,6 +79,8 @@ const CLASSROOM_MANAGEMENT: PermissionLayout[] = [ { label: "Upload to Classroom", key: "upload_classroom" }, { label: "Remove from Classroom", key: "remove_from_classroom" }, { label: "Delete Classroom", key: "delete_classroom" }, + { label: "View Student Record", key: "view_student_record" }, + { label: "Download Student Report", key: "download_student_record" }, ] const ENTITY_MANAGEMENT: PermissionLayout[] = [ diff --git a/src/pages/record.tsx b/src/pages/record.tsx index 557ebc86..6366a742 100644 --- a/src/pages/record.tsx +++ b/src/pages/record.tsx @@ -22,7 +22,7 @@ import { Assignment } from "@/interfaces/results"; import { getEntitiesUsers, getUsers } from "@/utils/users.be"; import { getAssignments, getEntitiesAssignments } from "@/utils/assignments.be"; import useGradingSystem from "@/hooks/useGrading"; -import { mapBy, redirect, serialize } from "@/utils"; +import { findBy, mapBy, redirect, serialize } from "@/utils"; import { getEntitiesWithRoles } from "@/utils/entities.be"; import { checkAccess } from "@/utils/permissions"; import { getGroups, getGroupsByEntities } from "@/utils/groups.be"; @@ -31,6 +31,7 @@ import { Grading } from "@/interfaces"; import { EntityWithRoles } from "@/interfaces/entity"; import CardList from "@/components/High/CardList"; import { requestUser } from "@/utils/api"; +import { useAllowedEntities } from "@/hooks/useEntityPermissions"; export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { const user = await requestUser(req, res) @@ -74,6 +75,7 @@ export default function History({ user, users, assignments, entities, gradingSys const [filter, setFilter] = useState(); const { data: stats, isLoading: isStatsLoading } = useFilterRecordsByUser(statsUserId || user?.id); + const allowedDownloadEntities = useAllowedEntities(user, entities, 'download_student_record') const renderPdfIcon = usePDFDownload("stats"); @@ -155,6 +157,9 @@ export default function History({ user, users, assignments, entities, gradingSys const customContent = (timestamp: string) => { const dateStats = groupedStats[timestamp]; + const statUser = findBy(users, 'id', dateStats[0]?.user) + + const canDownload = mapBy(statUser?.entities, 'id').some(e => mapBy(allowedDownloadEntities, 'id').includes(e)) return ( ); }; diff --git a/src/resources/entityPermissions.ts b/src/resources/entityPermissions.ts index 9bbeab9d..484672d1 100644 --- a/src/resources/entityPermissions.ts +++ b/src/resources/entityPermissions.ts @@ -61,7 +61,9 @@ export type RolePermission = "edit_grading_system" | "view_student_performance" | "upload_classroom" | - "download_user_list" + "download_user_list" | + "view_student_record" | + "download_student_record" export const DEFAULT_PERMISSIONS: RolePermission[] = [ "view_students", @@ -136,5 +138,7 @@ export const ADMIN_PERMISSIONS: RolePermission[] = [ "edit_grading_system", "view_student_performance", "upload_classroom", - "download_user_list" + "download_user_list", + "view_student_record", + "download_student_record" ] diff --git a/src/utils/groups.be.ts b/src/utils/groups.be.ts index 0c0a3536..fcfd3e67 100644 --- a/src/utils/groups.be.ts +++ b/src/utils/groups.be.ts @@ -3,6 +3,7 @@ import { WithEntity } from "@/interfaces/entity"; import { Assignment } from "@/interfaces/results"; import { CorporateUser, Group, GroupWithUsers, MasterCorporateUser, StudentUser, TeacherUser, Type, User } from "@/interfaces/user"; import client from "@/lib/mongodb"; +import { uniq } from "lodash"; import moment from "moment"; import { getLinkedUsers, getUser } from "./users.be"; import { getSpecificUsers } from "./users.be"; @@ -116,7 +117,7 @@ export const getUsersGroups = async (ids: string[]) => { export const convertToUsers = (group: Group, users: User[]): GroupWithUsers => Object.assign(group, { admin: users.find((u) => u.id === group.admin), - participants: group.participants.map((p) => users.find((u) => u.id === p)).filter((x) => !!x) as User[], + participants: uniq(group.participants).map((p) => users.find((u) => u.id === p)).filter((x) => !!x) as User[], }); export const getAllAssignersByCorporate = async (corporateID: string, type: Type): Promise => {