From 36be5267a20b588b7828c964f99dd65db4816145 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Thu, 30 Nov 2023 16:52:45 +0000 Subject: [PATCH 1/2] Set the Part 4 as undefined as well --- src/pages/(generation)/ListeningGeneration.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/(generation)/ListeningGeneration.tsx b/src/pages/(generation)/ListeningGeneration.tsx index 143d7223..bb74f6ca 100644 --- a/src/pages/(generation)/ListeningGeneration.tsx +++ b/src/pages/(generation)/ListeningGeneration.tsx @@ -144,6 +144,7 @@ const ListeningGeneration = () => { setPart1(undefined); setPart2(undefined); setPart3(undefined); + setPart4(undefined); setTypes([]); }) .catch((error) => { From 5c8867555da24eb929413ad003a129e7082a0a9b Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Sun, 3 Dec 2023 00:13:50 +0000 Subject: [PATCH 2/2] Added the option to view both the teachers and students of a corporate as well as the corporate of a student --- src/components/UserCard.tsx | 8 ++- src/dashboards/Admin.tsx | 79 +++++++++++++++++++++++---- src/dashboards/Agent.tsx | 4 +- src/dashboards/Corporate.tsx | 49 +++++++++++++++-- src/dashboards/Teacher.tsx | 2 +- src/pages/(admin)/Lists/UserList.tsx | 73 +++++++++++++++++++++++-- src/pages/list/users.tsx | 80 ++++++++++++++++++++++++++++ src/stores/listFilterStore.ts | 34 ++++++++++++ 8 files changed, 308 insertions(+), 21 deletions(-) create mode 100644 src/pages/list/users.tsx create mode 100644 src/stores/listFilterStore.ts diff --git a/src/components/UserCard.tsx b/src/components/UserCard.tsx index 931fe24a..82bca173 100644 --- a/src/components/UserCard.tsx +++ b/src/components/UserCard.tsx @@ -35,9 +35,10 @@ interface Props { onClose: (reload?: boolean) => void; onViewStudents?: () => void; onViewTeachers?: () => void; + onViewCorporate?: () => void; } -const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers}: Props) => { +const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers, onViewCorporate}: Props) => { const [expiryDate, setExpiryDate] = useState(user.subscriptionExpirationDate); const [type, setType] = useState(user.type); const [status, setStatus] = useState(user.status); @@ -456,6 +457,11 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers}:
+ {onViewCorporate && ( + + )} {onViewStudents && (
- + ); }; @@ -103,7 +108,7 @@ export default function AdminDashboard({user}: Props) {

Teachers ({users.filter(filter).length})

- + ); }; @@ -123,7 +128,7 @@ export default function AdminDashboard({user}: Props) {

Country Managers ({users.filter(filter).length})

- + ); }; @@ -140,7 +145,7 @@ export default function AdminDashboard({user}: Props) {

Corporate ({users.filter((x) => x.type === "corporate").length})

- x.type === "corporate"} /> + x.type === "corporate"]} /> ); @@ -159,7 +164,7 @@ export default function AdminDashboard({user}: Props) {

Inactive Students ({users.filter(filter).length})

- + ); }; @@ -179,7 +184,7 @@ export default function AdminDashboard({user}: Props) {

Inactive Corporate ({users.filter(filter).length})

- + ); }; @@ -378,9 +383,65 @@ export default function AdminDashboard({user}: Props) { if (shouldReload) reload(); }} onViewStudents={ - selectedUser.type === "corporate" || selectedUser.type === "teacher" ? () => setPage("students") : undefined + selectedUser.type === "corporate" || selectedUser.type === "teacher" + ? () => { + appendUserFilters({ + id: "view-students", + filter: (x: User) => x.type === "student", + }); + appendUserFilters({ + id: "belongs-to-admin", + filter: (x: User) => + groups + .filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id)) + .flatMap((g) => g.participants) + .includes(x.id), + }); + + router.push("/list/users"); + } + : undefined + } + onViewTeachers={ + selectedUser.type === "corporate" || selectedUser.type === "student" + ? () => { + appendUserFilters({ + id: "view-teachers", + filter: (x: User) => x.type === "teacher", + }); + appendUserFilters({ + id: "belongs-to-admin", + filter: (x: User) => + groups + .filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id)) + .flatMap((g) => g.participants) + .includes(x.id), + }); + + router.push("/list/users"); + } + : undefined + } + onViewCorporate={ + selectedUser.type === "teacher" || selectedUser.type === "student" + ? () => { + appendUserFilters({ + id: "view-corporate", + filter: (x: User) => x.type === "corporate", + }); + appendUserFilters({ + id: "belongs-to-admin", + filter: (x: User) => + groups + .filter((g) => g.participants.includes(selectedUser.id)) + .flatMap((g) => [g.admin, ...g.participants]) + .includes(x.id), + }); + + router.push("/list/users"); + } + : undefined } - onViewTeachers={selectedUser.type === "corporate" ? () => setPage("teachers") : undefined} user={selectedUser} /> diff --git a/src/dashboards/Agent.tsx b/src/dashboards/Agent.tsx index 0e91ec23..f951f04e 100644 --- a/src/dashboards/Agent.tsx +++ b/src/dashboards/Agent.tsx @@ -82,7 +82,7 @@ export default function AgentDashboard({user}: Props) {

Referred Corporate ({users.filter(filter).length})

- + ); }; @@ -102,7 +102,7 @@ export default function AgentDashboard({user}: Props) {

Referred Corporate ({users.filter(filter).length})

- + ); }; diff --git a/src/dashboards/Corporate.tsx b/src/dashboards/Corporate.tsx index ecf8fe40..54a50473 100644 --- a/src/dashboards/Corporate.tsx +++ b/src/dashboards/Corporate.tsx @@ -29,6 +29,8 @@ import {Module} from "@/interfaces"; import {groupByExam} from "@/utils/stats"; import IconCard from "./IconCard"; import GroupList from "@/pages/(admin)/Lists/GroupList"; +import useFilterStore from "@/stores/listFilterStore"; +import {useRouter} from "next/router"; interface Props { user: User; @@ -43,6 +45,9 @@ export default function CorporateDashboard({user}: Props) { const {users, reload} = useUsers(); const {groups} = useGroups(user.id); + const appendUserFilters = useFilterStore((state) => state.appendUserFilter); + const router = useRouter(); + useEffect(() => { setShowModal(!!selectedUser && page === ""); }, [selectedUser, page]); @@ -86,7 +91,7 @@ export default function CorporateDashboard({user}: Props) {

Students ({users.filter(filter).length})

- + ); }; @@ -113,7 +118,7 @@ export default function CorporateDashboard({user}: Props) {

Teachers ({users.filter(filter).length})

- + ); }; @@ -256,9 +261,45 @@ export default function CorporateDashboard({user}: Props) { if (shouldReload) reload(); }} onViewStudents={ - selectedUser.type === "corporate" || selectedUser.type === "teacher" ? () => setPage("students") : undefined + selectedUser.type === "corporate" || selectedUser.type === "teacher" + ? () => { + appendUserFilters({ + id: "view-students", + filter: (x: User) => x.type === "student", + }); + appendUserFilters({ + id: "belongs-to-admin", + filter: (x: User) => + groups + .filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id)) + .flatMap((g) => g.participants) + .includes(x.id), + }); + + router.push("/list/users"); + } + : undefined + } + onViewTeachers={ + selectedUser.type === "corporate" || selectedUser.type === "student" + ? () => { + appendUserFilters({ + id: "view-teachers", + filter: (x: User) => x.type === "teacher", + }); + appendUserFilters({ + id: "belongs-to-admin", + filter: (x: User) => + groups + .filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id)) + .flatMap((g) => g.participants) + .includes(x.id), + }); + + router.push("/list/users"); + } + : undefined } - onViewTeachers={selectedUser.type === "corporate" ? () => setPage("teachers") : undefined} user={selectedUser} /> diff --git a/src/dashboards/Teacher.tsx b/src/dashboards/Teacher.tsx index b0c01f0a..54dfede6 100644 --- a/src/dashboards/Teacher.tsx +++ b/src/dashboards/Teacher.tsx @@ -104,7 +104,7 @@ export default function TeacherDashboard({user}: Props) {

Students ({users.filter(filter).length})

- + ); }; diff --git a/src/pages/(admin)/Lists/UserList.tsx b/src/pages/(admin)/Lists/UserList.tsx index 7ed40bee..5d83a17c 100644 --- a/src/pages/(admin)/Lists/UserList.tsx +++ b/src/pages/(admin)/Lists/UserList.tsx @@ -17,17 +17,22 @@ import countryCodes from "country-codes-list"; import Modal from "@/components/Modal"; import UserCard from "@/components/UserCard"; import {USER_TYPE_LABELS} from "@/resources/user"; +import useFilterStore from "@/stores/listFilterStore"; +import {useRouter} from "next/router"; const columnHelper = createColumnHelper(); -export default function UserList({user, filter}: {user: User; filter?: (user: User) => boolean}) { +export default function UserList({user, filters = []}: {user: User; filters?: ((user: User) => boolean)[]}) { const [showDemographicInformation, setShowDemographicInformation] = useState(false); const [sorter, setSorter] = useState(); const [displayUsers, setDisplayUsers] = useState([]); const [selectedUser, setSelectedUser] = useState(); const {users, reload} = useUsers(); - const {groups} = useGroups(user ? user.id : undefined); + const {groups} = useGroups(user && (user?.type === "corporate" || user?.type === "teacher") ? user.id : undefined); + + const appendUserFilters = useFilterStore((state) => state.appendUserFilter); + const router = useRouter(); const expirationDateColor = (date: Date) => { const momentDate = moment(date); @@ -42,11 +47,11 @@ export default function UserList({user, filter}: {user: User; filter?: (user: Us useEffect(() => { if (user && users) { const filterUsers = - user.type === "corporate" || user.type === "student" + user.type === "corporate" || user.type === "teacher" ? users.filter((u) => groups.flatMap((g) => g.participants).includes(u.id)) : users; - const filteredUsers = filter ? filterUsers.filter(filter) : filterUsers; + const filteredUsers = filters.reduce((d, f) => d.filter(f), filterUsers); setDisplayUsers([...filteredUsers.sort(sortFunction)]); } @@ -457,6 +462,66 @@ export default function UserList({user, filter}: {user: User; filter?: (user: Us
{ + appendUserFilters({ + id: "view-students", + filter: (x: User) => x.type === "student", + }); + appendUserFilters({ + id: "belongs-to-admin", + filter: (x: User) => + groups + .filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id)) + .flatMap((g) => g.participants) + .includes(x.id), + }); + + router.push("/list/users"); + } + : undefined + } + onViewTeachers={ + selectedUser.type === "corporate" || selectedUser.type === "student" + ? () => { + appendUserFilters({ + id: "view-teachers", + filter: (x: User) => x.type === "teacher", + }); + appendUserFilters({ + id: "belongs-to-admin", + filter: (x: User) => + groups + .filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id)) + .flatMap((g) => g.participants) + .includes(x.id), + }); + + router.push("/list/users"); + } + : undefined + } + onViewCorporate={ + selectedUser.type === "teacher" || selectedUser.type === "student" + ? () => { + appendUserFilters({ + id: "view-corporate", + filter: (x: User) => x.type === "corporate", + }); + appendUserFilters({ + id: "belongs-to-admin", + filter: (x: User) => + groups + .filter((g) => g.participants.includes(selectedUser.id)) + .flatMap((g) => [g.admin, ...g.participants]) + .includes(x.id), + }); + + router.push("/list/users"); + } + : undefined + } onClose={(shouldReload) => { setSelectedUser(undefined); if (shouldReload) reload(); diff --git a/src/pages/list/users.tsx b/src/pages/list/users.tsx new file mode 100644 index 00000000..356ef810 --- /dev/null +++ b/src/pages/list/users.tsx @@ -0,0 +1,80 @@ +import Layout from "@/components/High/Layout"; +import useUser from "@/hooks/useUser"; +import useUsers from "@/hooks/useUsers"; +import {sessionOptions} from "@/lib/session"; +import useFilterStore from "@/stores/listFilterStore"; +import {withIronSessionSsr} from "iron-session/next"; +import Head from "next/head"; +import {useRouter} from "next/router"; +import {useEffect} from "react"; +import {BsArrowLeft} from "react-icons/bs"; +import {ToastContainer} from "react-toastify"; +import UserList from "../(admin)/Lists/UserList"; + +export const getServerSideProps = withIronSessionSsr(({req, res}) => { + const user = req.session.user; + + const envVariables: {[key: string]: string} = {}; + Object.keys(process.env) + .filter((x) => x.startsWith("NEXT_PUBLIC")) + .forEach((x: string) => { + envVariables[x] = process.env[x]!; + }); + + if (!user || !user.isVerified) { + res.setHeader("location", "/login"); + res.statusCode = 302; + res.end(); + return { + props: { + user: null, + envVariables, + }, + }; + } + + return { + props: {user: req.session.user, envVariables}, + }; +}, sessionOptions); + +export default function UsersListPage() { + const {user} = useUser(); + const {users} = useUsers(); + const [filters, clearFilters] = useFilterStore((state) => [state.userFilters, state.clearUserFilters]); + const router = useRouter(); + + return ( + <> + + EnCoach + + + + + + + {user && ( + +
+
{ + clearFilters(); + router.back(); + }} + className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"> + + Back +
+

Users ({filters.map((f) => f.filter).reduce((d, f) => d.filter(f), users).length})

+
+ + f.filter)} /> +
+ )} + + ); +} diff --git a/src/stores/listFilterStore.ts b/src/stores/listFilterStore.ts new file mode 100644 index 00000000..5cb24418 --- /dev/null +++ b/src/stores/listFilterStore.ts @@ -0,0 +1,34 @@ +import {Group, User} from "@/interfaces/user"; +import {create} from "zustand"; + +export type Filter = {id: string; filter: (x: T) => boolean}; + +export interface ListFilterState { + userFilters: Filter[]; + groupFilters: Filter[]; + appendUserFilter: (filter: Filter) => void; + removeUserFilter: (id: string) => void; + clearUserFilters: () => void; + appendGroupFilter: (filter: Filter) => void; + removeGroupFilter: (id: string) => void; + clearGroupFilters: () => void; + reset: () => void; +} + +export const initialState = { + userFilters: [], + groupFilters: [], +}; + +const useFilterStore = create((set) => ({ + ...initialState, + appendUserFilter: (filter: Filter) => set((state) => ({userFilters: [...state.userFilters.filter((f) => f.id !== filter.id), filter]})), + appendGroupFilter: (filter: Filter) => set((state) => ({groupFilters: [...state.groupFilters.filter((f) => f.id !== filter.id), filter]})), + removeUserFilter: (id: string) => set((state) => ({userFilters: state.userFilters.filter((x) => x.id !== id)})), + removeGroupFilter: (id: string) => set((state) => ({groupFilters: state.groupFilters.filter((x) => x.id !== id)})), + clearUserFilters: () => set({userFilters: []}), + clearGroupFilters: () => set({groupFilters: []}), + reset: () => set(() => initialState), +})); + +export default useFilterStore;